SQLite 开启 WAL 后,怎样验证备份真的包含最新数据
把一个正在运行的 SQLite 数据库复制到备份目录,副本可能能正常打开,甚至能通过完整性检查,却少了最近提交的数据。开启 WAL 后,已提交的变更可以暂存在 数据库名-wal 文件里,等检查点再写回主文件。只复制主文件,可能拿到一个结构完好但已经过时的副本。
下面用 Python 标准库在临时目录复现这个现象。先把第一条记录写回主文件,再提交第二条记录,同时让源连接保持打开。对比文件复制与 SQLite 在线备份的结果。
import shutil
import sqlite3
from contextlib import closing
from pathlib import Path
from tempfile import TemporaryDirectory
def inspect(path):
uri = path.as_uri() + "?mode=ro"
with closing(sqlite3.connect(uri, uri=True)) as db:
check = db.execute("PRAGMA integrity_check").fetchone()[0]
rows = db.execute("SELECT id FROM events ORDER BY id").fetchall()
return check, [row[0] for row in rows]
with TemporaryDirectory() as folder:
root = Path(folder)
source = root / "live.db"
copied = root / "copied.db"
backup = root / "backup.db"
with closing(sqlite3.connect(source)) as db:
mode = db.execute("PRAGMA journal_mode=WAL").fetchone()[0]
assert mode == "wal"
db.execute("PRAGMA wal_autocheckpoint=0")
db.execute("CREATE TABLE events (id INTEGER PRIMARY KEY)")
db.execute("INSERT INTO events VALUES (1)")
db.commit()
db.execute("PRAGMA wal_checkpoint(TRUNCATE)").fetchone()
db.execute("INSERT INTO events VALUES (2)")
db.commit()
shutil.copyfile(source, copied)
with closing(sqlite3.connect(backup)) as destination:
db.backup(destination)
print("只复制主文件", inspect(copied))
print("SQLite 在线备份", inspect(backup))
只复制主文件 ('ok', [1])
SQLite 在线备份 ('ok', [1, 2])
两个副本都返回 ok,因为 integrity_check 检查的是数据库内部结构。它不会替你核对备份应包含哪批业务数据。这也是恢复演练时要再查一条业务指标的原因,例如备份时已确认提交的批次号,或一组带时间戳的记录。示例关闭自动检查点并保持源连接打开,只是为了让这个现象稳定出现。真实应用无需修改这些设置来做备份。
实际使用时,让 Connection.backup() 写入新的目标文件,关闭目标连接后再验证。源库可以用 mode=ro 打开,这样路径写错时会报错,不会意外创建一个空库。先对副本执行完整性检查,再抽查关键业务记录。存在外键时,可另外执行 PRAGMA foreign_key_check。每一步都通过后,再将副本移到备份目录并记录完成时间。
如果应用一直有新写入,事后比较源库与副本的总行数容易误报。可以改用备份前已提交且之后不会变化的记录作为基准。恢复时先在独立目录打开备份,让应用执行一次只读查询,确认实际读取到预期记录,再安排正式切换。
参考资料为 SQLite 官方文档的《SQLite Backup API》《Write-Ahead Logging》,以及 Python 标准库文档 sqlite3 中 Connection.backup() 一节。
- 点赞
- 收藏
- 关注作者
评论(0)