Каков наилучший способ сделать резервную копию базы данных sqlite? - PullRequest
0 голосов
/ 24 мая 2019

Как лучше всего создать резервную копию базы данных sqlite на Android, чтобы данные оставались в Google Drive пользователя? В идеале я хочу делать инкрементные резервные копии вместо постоянного резервного копирования всех данных.

В настоящее время я использую стратегию - резервное копирование всех трех файлов базы данных {db_name}.db, {db_name}-wal.db и {db_name}-shm.db на пользовательский диск Google один раз в день, а затем восстановление всех этих файлов для пользователя, когда пользователь входит в новый устройство. Вот часть кода WorkManager, которую я использую для резервного копирования: -

        File databaseFile = new File(context.getDatabasePath("db1").getAbsolutePath());
        File databaseWalFile = new File(context.getDatabasePath("db1-wal").getAbsolutePath());
        File databaseShmFile = new File(context.getDatabasePath("db1-shm").getAbsolutePath());
        if (!databaseFile.exists() || !databaseWalFile.exists() || !databaseShmFile.exists()) {
            return Result.SUCCESS;
        }
        long totalBackupSize = databaseFile.length() + databaseWalFile.length() + databaseShmFile.length();
        GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(getApplicationContext());
        DriveResourceClient driveResourceClient = Drive.getDriveResourceClient(getApplicationContext(), account);
        final Task<DriveFolder> appFolderTask = driveResourceClient.getAppFolder();
        final Task<DriveContents> createContentsDbTask = driveResourceClient.createContents();
        final Task<DriveContents> createContentsDbWalTask = driveResourceClient.createContents();
        final Task<DriveContents> createContentsDbShmTask = driveResourceClient.createContents();
        Tasks.whenAll(appFolderTask, createContentsDbTask, createContentsDbWalTask, createContentsDbShmTask)
                .continueWithTask(task -> {
                    DriveFolder parent = appFolderTask.getResult();
                    DriveContents dbContents = createContentsDbTask.getResult();
                    DriveContents dbWalContents = createContentsDbWalTask.getResult();
                    DriveContents dbShmContents = createContentsDbShmTask.getResult();
                    if (dbContents != null && dbWalContents != null && dbShmContents != null) {
                        Utility.copy(new FileInputStream(databaseFile), dbContents.getOutputStream());
                        Utility.copy(new FileInputStream(databaseWalFile), dbWalContents.getOutputStream());
                        Utility.copy(new FileInputStream(databaseShmFile), dbShmContents.getOutputStream());
                        MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
                                .setTitle(AppConstants.GoogleDriveBackupDbFileName)
                                .setMimeType(AppConstants.DatabaseMimeType)
                                .build();
                        MetadataChangeSet changeWalSet = new MetadataChangeSet.Builder()
                                .setTitle(AppConstants.GoogleDriveBackupDbWalFileName)
                                .setMimeType(AppConstants.DatabaseMimeType)
                                .build();
                        MetadataChangeSet changeShmSet = new MetadataChangeSet.Builder()
                                .setTitle(AppConstants.GoogleDriveBackupDbShmFileName)
                                .setMimeType(AppConstants.DatabaseMimeType)
                                .build();
                        return Tasks.whenAll(driveResourceClient.createFile(parent, changeSet, dbContents),
                                driveResourceClient.createFile(parent, changeWalSet, dbWalContents),
                                driveResourceClient.createFile(parent, changeShmSet, dbShmContents));
                    }
                    TaskCompletionSource<DriveFile> tcs = new TaskCompletionSource<>();
                    tcs.setException(new Exception("Failed to copy database file"));
                    return Tasks.whenAll(tcs.getTask());
                }).addOnSuccessListener(driveFile -> {
                    SharedPreferences.Editor editor = context.getSharedPreferences(PreferenceConstants.INSTANCE.getPreferenceName(), Context.MODE_PRIVATE).edit();
                    editor.putLong(PreferenceConstants.INSTANCE.getLastGoogleDriveBackupTime(), new Date().getTime());
                    editor.putLong(PreferenceConstants.INSTANCE.getFileSize(), totalBackupSize);
                    editor.apply();
                    Log.v("BackupWorker", "Backup successful");
                    result[0] = Result.SUCCESS;
                    countDownLatch.countDown();
                }).addOnFailureListener(e -> {
                    Log.e("BackupWorker", "Unable to create file", e);
                    result[0] = Result.FAILURE;
                    countDownLatch.countDown();
                });

Резервное копирование и восстановление с использованием вышеуказанной стратегии, но мои опасения: -

  • Что если внутренняя реализация изменится в будущем, когда нам нужно будет сделать резервные копии некоторых файлов, кроме wal и shm? Простое создание резервной копии основного файла базы данных не помогло мне.
  • Эта стратегия неэффективна, поскольку требует, чтобы я постоянно делал резервную копию всей базы данных. Можно ли делать инкрементные резервные копии здесь?

Я подумал о том, чтобы хранить резервные копии в файлах CSV и хранить их на Google Диске. И тогда я мог бы просто записать изменения в файл CSV для каждой последующей резервной копии. Однако я чувствую, что это немного увеличит усилия разработчика. Есть ли лучшее решение?

1 Ответ

1 голос
/ 24 мая 2019

Простое создание резервной копии основного файла базы данных не работает для я.

Если вы правильно отметили базу данных, используя опцию TRUNCATE перед выполнением резервного копирования, тогда не нужно создавать резервные копии файлов -wal и -shm (так как они будут пустыми).

Также, если вы закроете все соединения, это также должно завершить контрольную точку. Например, рассмотрим следующее (где logDBSizeInfo выводит размеры файлов, addsomeData и deleteSomeData делают как говорится)

    mDBHlpr = new DBHelper(this);
    logDBSizeInfo(this,DBHelper.DBNAME);
    if (DatabaseUtils.queryNumEntries(mDBHlpr.getWritableDatabase(),"table1") < 1) {
        addSomeData(100000);
        deleteSomeData(1000);
    } else {
        addSomeData(1000);
        deleteSomeData(100);
    }
    logDBSizeInfo(this,DBHelper.DBNAME);
    mDBHlpr.close();
    //checkpointIfWALEnabled(this,DBHelper.DBNAME);
    logDBSizeInfo(this,DBHelper.DBNAME);

Результат: -

05-24 20:25:47.195 D/DBSIZEINFO: Database Size is 34312192
05-24 20:25:47.195 D/DBSIZEINFO: Database -wal file Size is 0 path is /data/user/0/aso.so56286308backupwal/databases/mydb-wal
05-24 20:25:47.196 D/DBSIZEINFO: Database -shm file Size is 32768 path is /data/user/0/aso.so56286308backupwal/databases/mydb-shm
05-24 20:25:47.199 D/ADDSOMEDATA: Adding about 1000 rows
05-24 20:25:47.365 D/ADDSOMEDATA: Deleting about 100 rows
05-24 20:25:47.397 D/DBSIZEINFO: Database Size is 34656256
05-24 20:25:47.397 D/DBSIZEINFO: Database -wal file Size is 420272 path is /data/user/0/aso.so56286308backupwal/databases/mydb-wal
05-24 20:25:47.397 D/DBSIZEINFO: Database -shm file Size is 32768 path is /data/user/0/aso.so56286308backupwal/databases/mydb-shm

<<<<<<<<<<After Close>>>>>>>>>>

05-24 20:25:47.400 D/DBSIZEINFO: Database Size is 34656256
05-24 20:25:47.400 D/DBSIZEINFO: Database -wal file does not exist
05-24 20:25:47.400 D/DBSIZEINFO: Database file -shm does not exist

Я использую следующее для проверки контрольной точки файла перед резервным копированием (только файл базы данных): -

private void checkpointIfWALEnabled(Context context, String databaseName) {
    Cursor csr;
    int wal_busy = -99, wal_log = -99, wal_checkpointed = -99;
    SQLiteDatabase db = SQLiteDatabase.openDatabase(context.getDatabasePath(databaseName).getPath(),null,SQLiteDatabase.OPEN_READWRITE);
    csr = db.rawQuery("PRAGMA journal_mode",null);
    if (csr.moveToFirst()) {
        String mode = csr.getString(0);
        if (mode.toLowerCase().equals("wal")) {
            csr = db.rawQuery("PRAGMA wal_checkpoint",null);
            if (csr.moveToFirst()) {
                wal_busy = csr.getInt(0);
                wal_log = csr.getInt(1);
                wal_checkpointed = csr.getInt(2);
            }
            csr = db.rawQuery("PRAGMA wal_checkpoint(TRUNCATE)",null);
            csr.getCount();
            csr = db.rawQuery("PRAGMA wal_checkpoint",null);
            if (csr.moveToFirst()) {
                wal_busy = csr.getInt(0);
                wal_log = csr.getInt(1);
                wal_checkpointed = csr.getInt(2);
            }
        }
    }
    csr.close();
    db.close();
}

Другим вариантом, который может уменьшить размер резервной копии, будет использование VACUUM INTO . Я не пробовал этот подход. Тем не менее, ведение журнала используется, поэтому может быть целесообразным впоследствии использовать контрольную точку (затем вы можете использовать файл не только в качестве резервной копии, но и для замены исходного файла).

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

...