数据库作为二进制文件,当内部有部分数据不一致或丢失时,可能会发生数据库损坏。数据不一致或丢失的原因较多,可能是代码问题、操作系统或文件系统故障、磁盘损坏等等。从根源上,数据库损坏是不可能完全避免的。

因此,WCDB Swift 内建了修复工具,可以尽最大限度地将数据找回,减少数据丢失。

损坏检测

监控与错误处理一章,已经提到了监控和处理错误信息了。损坏也可以用同样地方式监控,当错误类型为 SQLite 且错误码为 11 或 26 时,代表发生了数据库损坏,然后可以根据 tag 确认损坏的数据库,而后进行处理。

  1. Database.globalTrace(ofError: { (error: WCDBSwift.Error) in
  2. if error.type == .sqlite && (error.code.value == 11 || error.code.value == 26) {
  3. print("Tag: \(error.tag) is corrupted")
  4. }
  5. })

元数据备份

在有备份的情况,修复工具的能力将大大提升。在数据库内的数据发生变化时,元数据备份有可能会过期。因此建议在子线程定期对其备份。

  1. DispatchQueue.global(qos: .background).async {
  2. Timer.scheduledTimer(withTimeInterval: 5 * 60, repeats: true) {_ in
  3. let backupPassword = "backupPassword".data(using: .ascii)
  4. try? database.backup(withKey: backupPassword)
  5. }
  6. }
元数据通常只有几 kb 大小,且属于读操作,可以与其他操作并发执行。因此备份不会对性能产生大的影响。

数据修复

数据库修复通过 recover(fromPath:withPageSize:databaseKey:backupKey:) 接口完成。它将尝试从已损坏的数据库中读出数据,并插入到新数据库中。其函数原型为:

  1. func recover(fromPath source: String, // 已损坏的数据库路径
  2. withPageSize pageSize: Int32 = 4096, // 已损坏的数据库的page size
  3. databaseKey: Data? = nil, // 已损坏的数据库的密码,若未加密,则为 nil
  4. backupKey: Data? = nil) throws // 元数据备份的密码,若备份未加密,则为 nil

修复过程根据数据库的大小不同,需要一定的时间。建议在界面上提示等待,并在子线程进行修复。

  1. //view.startLoading()
  2. DispatchQueue.global(qos: .background).async {
  3. let newDatabase = Database(withPath: "newPath")
  4. try? newDatabase.recover(fromPath: pathToCorruptedDatabase,
  5. withPageSize: 4096,
  6. databaseKey: corruptedDatabaseKey,
  7. backupKey: backupKey)
  8. //DispatchQueue.main.async {
  9. // view.stopLoading()
  10. //}
  11. }
对于可再生的数据,如可从服务端重新拉取的数据,直接将数据库删掉重建是更好的恢复手段。