android - 备份sqlite数据库的最佳方法是什么?
问题描述
在 Android 上备份 sqlite 数据库以使数据保留在用户的 Google Drive 中的最佳方法是什么?理想情况下,我希望进行增量备份,而不是一直备份整个数据。
目前我使用的策略是备份所有三个数据库文件{db_name}.db
,{db_name}-wal.db
并{db_name}-shm.db
每天备份一次用户的谷歌驱动器,然后在用户登录新设备时为用户恢复所有这些文件。这是我用于备份的 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 Drive 上。然后我可以为每个后续备份将更改写入 CSV 文件。但是我觉得这会大大增加开发工作量。有更好的解决方案吗?
解决方案
简单地备份主数据库文件对我不起作用。
如果您在执行备份之前使用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。我没有尝试过这种方法。但是,使用了日志记录,因此之后检查点也可能是明智的(然后您可以使用该文件不仅作为备份,还可以替换原始文件)。
数据库的增量备份可能会被证明是不切实际且更耗时的,除非您基本上已经构建了事务日志记录,因此可能不会问这个问题。一般来说,增量备份系统是由相对容易检测到的更改驱动的。
推荐阅读
- python - 如何在 Python 中找到列表的最大值?
- python - 如何使用从字符串中抓取的一些运算符
- java - 在 Java 中反转 PriorityQueue 顺序的最快方法
- react-native - 在 react-native 的应用程序中处于睡眠模式或不活动状态后,模态不会打开
- reactjs - 传递整个对象时是否可以避免重新渲染所有输入?
- javascript - MongoDB 无法使用 findOneAndUpdate() 推送到数组
- ip-address - 您的服务器拥有静态 IP 地址有什么好处?
- mysql - MySQL 中的触发器问题
- javascript - 如何在firebase中的phoneNumber链接后重定向?
- php - PHP 条件适用于 Chrome,但不适用于 FF 或 Safari