当 room android db 损坏时会发生什么?
What happens when room android db gets corrupted?
在大型应用程序中,我的数据库文件(通过 android 房间库创建)有可能损坏。对于这样的用户,后备方案是什么?
room 会删除 db 文件并在生产模式下从头开始重新创建,还是我必须自己处理?
For such a user whats the fallback ?
备份和恢复,注意当有任何未完成的事务时不应进行备份。最好确保如果在 WAL 模式下(这是有空间的默认模式)数据库已完全提交(即 WAL 文件为空或不存在)。
- 备份可以是一个简单的文件副本,或者您可以使用 VACUUM INTO 后者具有潜在释放 space 的优点,但缺点是它可能会占用更多资源。
您可以维护其他数据库,在主数据库更改时将更改应用于其他数据库。显然会有开销。这样做的好处是,如果发生损坏,则不太可能在其他数据库中发生。
您可以维护允许回滚和前滚更改的日志。后者用于从备份前滚到损坏点或接近损坏点。
Will room delete the db file and re-create from scratch in production mode or will I have to handle that myself ?
如果检测到(打开时)则是:-
onCorruption
检测到数据库损坏时调用的方法。 默认实现将删除数据库文件。
What happens when room android db gets corrupted?
这是文件损坏的示例
2021-10-27 10:36:19.281 7930-7930/a.a.so69722729kotlinroomcorrupt E/SQLiteLog: (26) file is not a database
2021-10-27 10:36:19.285 7930-7930/a.a.so69722729kotlinroomcorrupt E/SupportSQLite: Corruption reported by sqlite on database: /data/user/0/a.a.so69722729kotlinroomcorrupt/databases/thedatabase
2021-10-27 10:36:19.286 7930-7930/a.a.so69722729kotlinroomcorrupt W/SupportSQLite: deleting the database file: /data/user/0/a.a.so69722729kotlinroomcorrupt/databases/thedatabase
2021-10-27 10:36:19.306 7930-7930/a.a.so69722729kotlinroomcorrupt D/TAG: onCreate Invoked.
2021-10-27 10:36:19.312 7930-7930/a.a.so69722729kotlinroomcorrupt D/TAG: onOpen Invoked.
从回调中可以看出,删除文件后会调用 onCreate,从而创建一个新的空数据库。
上面使用的代码是:-
一个简单的@Entity Something :-
@Entity
data class Something(
@PrimaryKey
val id: Long?=null,
val something: String
)
一个简单的@Dao AllDao
@Dao
abstract class AllDao {
@Insert
abstract fun insert(something: Something)
@Query("SELECT * FROM something")
abstract fun getAllFromSomething(): List<Something>
}
@Database TheDatabase
@Database(entities = [Something::class],version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
const val DATABASENAME = "thedatabase"
const val TAG = "DBINFO"
private var instance: TheDatabase? = null
var existed: Boolean = false
fun getInstance(context: Context): TheDatabase {
existed = exists(context)
if (exists(context)) {
Log.d(TAG,"Database exists so corrupting it before room opens the database.")
corruptDatabase(context)
}
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java, DATABASENAME)
.allowMainThreadQueries()
.addCallback(cb)
.build()
}
instance!!.openHelper.writableDatabase // Force open
return instance as TheDatabase
}
object cb: Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Log.d("TAG","onCreate Invoked.")
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
Log.d("TAG","onDestructiveMigration Invoked.")
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
Log.d("TAG","onOpen Invoked.")
}
}
fun exists(context: Context): Boolean {
val db = File(context.getDatabasePath(DATABASENAME).path)
return db.exists()
}
/**
* Corrupt the database by
* copying the file via buffered reads and write
* BUT only write part of a buffer
* AND skip the 3rd block
* Furthermore, delete the -wal file (possible that this )
* Note that often it is the -wal file deletion that corrupts
*/
fun corruptDatabase(context: Context) {
Log.d("TAG","corruptDatabase Invoked.")
var db: File = File(context.getDatabasePath(DATABASENAME).path)
logFileInfo(db,"Initial")
var tempdb = File(context.getDatabasePath("temp" + DATABASENAME).path)
logFileInfo(tempdb,"Initial")
val blksize = 128
var buffer = ByteArray(blksize)
var i: InputStream
var o: FileOutputStream
try {
i = FileInputStream(db)
o = FileOutputStream(tempdb)
var blocks = 0;
var writes = 0;
while (i.read(buffer) > 0) {
if(blocks++ % 2 == 1) {
writes++
o.write(buffer,buffer.size / 4,buffer.size / 4)
}
}
Log.d(TAG,"${blocks} Read ${writes} Written")
o.flush()
o.close()
i.close()
db = File(context.getDatabasePath(DATABASENAME).path)
logFileInfo(db,"After copy")
tempdb = File(context.getDatabasePath("temp${DATABASENAME}").path)
logFileInfo(tempdb,"After copy")
} catch (e: IOException) {
e.printStackTrace()
}
db.delete()
//(context.getDatabasePath(DATABASENAME+"-wal")).delete()
logFileInfo(db,"After delete")
tempdb.renameTo(context.getDatabasePath(DATABASENAME))
logFileInfo(tempdb,"After rename")
logFileInfo(context.getDatabasePath(DATABASENAME),"After rename/new file")
}
fun logFileInfo(file: File, prefix: String) {
Log.d(TAG,"${prefix} FileName is ${file.name}\n\tpath is ${file.path}\n\tsize is ${file.totalSpace} frespace is ${file.freeSpace}")
}
}
}
最后调用 Activity MainActivity
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this);
dao = db.getAllDao()
dao.insert(Something(something = "Something1 " + TheDatabase.existed))
dao.insert(Something(something = "Something2 " + TheDatabase.existed))
for(s: Something in dao.getAllFromSomething()) {
Log.d(TheDatabase.TAG,"Something with ID " + s.id + " is " + s.something)
}
}
}
- 请注意,上面的数据库损坏的是 -wal 删除。对于被删除的块来说,它是非常有弹性的,至少对于较小的数据库来说是这样,在这些数据库中,WAL 文件的大小足以通过应用存储在其中的更改来恢复。然而,基于上述但插入第二个 table 和 100000 行的测试,部分块写入和丢失的块写入确实会破坏数据库。
在大型应用程序中,我的数据库文件(通过 android 房间库创建)有可能损坏。对于这样的用户,后备方案是什么?
room 会删除 db 文件并在生产模式下从头开始重新创建,还是我必须自己处理?
For such a user whats the fallback ?
备份和恢复,注意当有任何未完成的事务时不应进行备份。最好确保如果在 WAL 模式下(这是有空间的默认模式)数据库已完全提交(即 WAL 文件为空或不存在)。
- 备份可以是一个简单的文件副本,或者您可以使用 VACUUM INTO 后者具有潜在释放 space 的优点,但缺点是它可能会占用更多资源。
您可以维护其他数据库,在主数据库更改时将更改应用于其他数据库。显然会有开销。这样做的好处是,如果发生损坏,则不太可能在其他数据库中发生。
您可以维护允许回滚和前滚更改的日志。后者用于从备份前滚到损坏点或接近损坏点。
Will room delete the db file and re-create from scratch in production mode or will I have to handle that myself ?
如果检测到(打开时)则是:-
onCorruption 检测到数据库损坏时调用的方法。 默认实现将删除数据库文件。
What happens when room android db gets corrupted?
这是文件损坏的示例
2021-10-27 10:36:19.281 7930-7930/a.a.so69722729kotlinroomcorrupt E/SQLiteLog: (26) file is not a database
2021-10-27 10:36:19.285 7930-7930/a.a.so69722729kotlinroomcorrupt E/SupportSQLite: Corruption reported by sqlite on database: /data/user/0/a.a.so69722729kotlinroomcorrupt/databases/thedatabase
2021-10-27 10:36:19.286 7930-7930/a.a.so69722729kotlinroomcorrupt W/SupportSQLite: deleting the database file: /data/user/0/a.a.so69722729kotlinroomcorrupt/databases/thedatabase
2021-10-27 10:36:19.306 7930-7930/a.a.so69722729kotlinroomcorrupt D/TAG: onCreate Invoked.
2021-10-27 10:36:19.312 7930-7930/a.a.so69722729kotlinroomcorrupt D/TAG: onOpen Invoked.
从回调中可以看出,删除文件后会调用 onCreate,从而创建一个新的空数据库。
上面使用的代码是:-
一个简单的@Entity Something :-
@Entity
data class Something(
@PrimaryKey
val id: Long?=null,
val something: String
)
一个简单的@Dao AllDao
@Dao
abstract class AllDao {
@Insert
abstract fun insert(something: Something)
@Query("SELECT * FROM something")
abstract fun getAllFromSomething(): List<Something>
}
@Database TheDatabase
@Database(entities = [Something::class],version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
const val DATABASENAME = "thedatabase"
const val TAG = "DBINFO"
private var instance: TheDatabase? = null
var existed: Boolean = false
fun getInstance(context: Context): TheDatabase {
existed = exists(context)
if (exists(context)) {
Log.d(TAG,"Database exists so corrupting it before room opens the database.")
corruptDatabase(context)
}
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java, DATABASENAME)
.allowMainThreadQueries()
.addCallback(cb)
.build()
}
instance!!.openHelper.writableDatabase // Force open
return instance as TheDatabase
}
object cb: Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
Log.d("TAG","onCreate Invoked.")
}
override fun onDestructiveMigration(db: SupportSQLiteDatabase) {
super.onDestructiveMigration(db)
Log.d("TAG","onDestructiveMigration Invoked.")
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
Log.d("TAG","onOpen Invoked.")
}
}
fun exists(context: Context): Boolean {
val db = File(context.getDatabasePath(DATABASENAME).path)
return db.exists()
}
/**
* Corrupt the database by
* copying the file via buffered reads and write
* BUT only write part of a buffer
* AND skip the 3rd block
* Furthermore, delete the -wal file (possible that this )
* Note that often it is the -wal file deletion that corrupts
*/
fun corruptDatabase(context: Context) {
Log.d("TAG","corruptDatabase Invoked.")
var db: File = File(context.getDatabasePath(DATABASENAME).path)
logFileInfo(db,"Initial")
var tempdb = File(context.getDatabasePath("temp" + DATABASENAME).path)
logFileInfo(tempdb,"Initial")
val blksize = 128
var buffer = ByteArray(blksize)
var i: InputStream
var o: FileOutputStream
try {
i = FileInputStream(db)
o = FileOutputStream(tempdb)
var blocks = 0;
var writes = 0;
while (i.read(buffer) > 0) {
if(blocks++ % 2 == 1) {
writes++
o.write(buffer,buffer.size / 4,buffer.size / 4)
}
}
Log.d(TAG,"${blocks} Read ${writes} Written")
o.flush()
o.close()
i.close()
db = File(context.getDatabasePath(DATABASENAME).path)
logFileInfo(db,"After copy")
tempdb = File(context.getDatabasePath("temp${DATABASENAME}").path)
logFileInfo(tempdb,"After copy")
} catch (e: IOException) {
e.printStackTrace()
}
db.delete()
//(context.getDatabasePath(DATABASENAME+"-wal")).delete()
logFileInfo(db,"After delete")
tempdb.renameTo(context.getDatabasePath(DATABASENAME))
logFileInfo(tempdb,"After rename")
logFileInfo(context.getDatabasePath(DATABASENAME),"After rename/new file")
}
fun logFileInfo(file: File, prefix: String) {
Log.d(TAG,"${prefix} FileName is ${file.name}\n\tpath is ${file.path}\n\tsize is ${file.totalSpace} frespace is ${file.freeSpace}")
}
}
}
最后调用 Activity MainActivity
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this);
dao = db.getAllDao()
dao.insert(Something(something = "Something1 " + TheDatabase.existed))
dao.insert(Something(something = "Something2 " + TheDatabase.existed))
for(s: Something in dao.getAllFromSomething()) {
Log.d(TheDatabase.TAG,"Something with ID " + s.id + " is " + s.something)
}
}
}
- 请注意,上面的数据库损坏的是 -wal 删除。对于被删除的块来说,它是非常有弹性的,至少对于较小的数据库来说是这样,在这些数据库中,WAL 文件的大小足以通过应用存储在其中的更改来恢复。然而,基于上述但插入第二个 table 和 100000 行的测试,部分块写入和丢失的块写入确实会破坏数据库。