为什么适合这个场景
牛马日记的核心场景是单人使用的 Windows 桌面工具。任务、项目和工作记录彼此有关联,需要按日期、状态与项目检索,同时又不需要为一次本地查看启动一个独立数据库服务。SQLite 与这种边界相匹配。
从源码可以确认,应用通过 Python 的 sqlite3 访问数据库,并启用外键约束。这个选择减少了安装步骤,但不意味着数据维护问题自动消失。结构升级、备份、恢复和错误处理仍然需要明确设计。
把相关修改放进事务
假设一次操作要创建项目,再关联一条任务。中途失败时,不能只留下半次操作。下面用内存数据库演示事务,不读取任何真实工作数据,也不是项目源码摘录:
import sqlite3
with sqlite3.connect(":memory:") as db:
db.execute("PRAGMA foreign_keys = ON")
db.execute("CREATE TABLE project (id INTEGER PRIMARY KEY, name TEXT)")
db.execute("CREATE TABLE task (title TEXT, project_id INTEGER REFERENCES project(id))")
with db:
cursor = db.execute("INSERT INTO project(name) VALUES (?)", ("示例项目",))
db.execute(
"INSERT INTO task(title, project_id) VALUES (?, ?)",
("整理一份设计笔记", cursor.lastrowid),
)
参数绑定把数据和 SQL 语句分开。事务则让其中的写入作为一个整体提交或回滚。真实应用还应捕获异常并保留用户输入,而不是在失败后继续显示“已保存”。
一致性快照比复制文件更可靠
正在运行的数据库可能还有事务或日志文件。直接复制主文件并不能普遍保证拿到一致的备份。Python 的 SQLite Backup API 可以从数据库连接生成快照。下面仍是独立示例,会在当前目录创建一个演示备份文件:
from pathlib import Path
import sqlite3
backup_path = Path("example-backup.db")
if backup_path.exists():
raise FileExistsError("示例备份已存在,请换一个文件名")
with sqlite3.connect(":memory:") as source:
source.execute("CREATE TABLE note (body TEXT)")
source.execute("INSERT INTO note VALUES (?)", ("这是一条演示记录",))
source.commit()
with sqlite3.connect(backup_path) as target:
source.backup(target)
备份完成只证明有了一个文件,不证明恢复链路可靠。还需要在隔离位置检查结构、完整性和版本,再测试恢复;不要拿唯一一份真实数据库做实验。
结构版本是兼容性边界
当旧程序遇到较新的数据库结构,直接猜测读取可能造成损坏。项目使用结构版本检查,对不支持的版本停止操作。这比“尽量打开”更符合长期数据保护的目标。
导入也不应悄悄合并或覆盖。需要明确告知替换范围,先保留可回滚副本,再处理连接关闭和失败恢复。
本地优先也有边界
本地文件不等于已加密,也不等于永不丢失。电脑故障、误删和备份介质丢失仍然存在。SQLite 也不自动解决多端冲突;如果未来增加同步,需要另外设计变更标识与合并规则。
可继续阅读多端协同规划。技术接口说明参考 Python sqlite3 文档及 SQLite 在线备份说明。