Что происходит, когда room android db повреждается?

#android #kotlin #android-sqlite #android-room

Вопрос:

В крупномасштабном приложении есть вероятность, что мой файл db (созданный с помощью android room lib) будет поврежден. Для такого пользователя какой запасной вариант?

room удалит файл db и создаст заново с нуля в рабочем режиме или мне придется справляться с этим самостоятельно?

Ответ №1:

Для такого пользователя какой запасной вариант?

Резервное копирование и восстановление, отмечая, что резервные копии не должны приниматься, когда есть какие-либо открытые транзакции. Лучше всего убедиться, что в режиме WAL (который является режимом по умолчанию для room) база данных полностью зафиксирована (т. Е. Файл WAL пуст или не существует).

  • Резервная копия может быть простой копией файла, или вы можете использовать ВАКУУМ В последнем, что потенциально освобождает место, но недостатком является то, что это может быть более ресурсоемким.

Вы можете поддерживать дополнительные базы данных, в которых изменения применяются к другим базам данных по мере изменения основной базы данных. Очевидно, что будут накладные расходы. Это имело бы то преимущество, что в случае возникновения повреждения вероятность его возникновения в других базах данных была бы меньше.

Вы могли бы вести журнал, который позволял откатывать и откатывать изменения. Последнее используется для перехода от резервной копии к точке повреждения или близко к точке повреждения.

room удалит файл db и создаст заново с нуля в рабочем режиме или мне придется справляться с этим самостоятельно?

если обнаружено (при открытии), то да :-

onCorruption Метод, вызываемый при обнаружении повреждения базы данных. Реализация по умолчанию удалит файл базы данных.

https://developer.android.com/reference/androidx/sqlite/db/SupportSQLiteOpenHelper .Обратный вызов #onCorruption(androidx.sqlite.db.SupportSQLiteDatabase)

Что происходит, когда room android db повреждается?

вот пример повреждения файла

 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 Что-то :-

 @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}ntpath is ${file.path}ntsize is ${file.totalSpace} frespace is ${file.freeSpace}")
        }
    }
}
 

Наконец, вызывающая активность 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 достаточен для восстановления как такового путем применения изменений, сохраненных внутри. Однако при тестировании, основанном на приведенном выше, но со второй таблицей и вставленными 100000 строками, частичная запись блока и пропущенные записи блока повреждают базу данных.