一个做了14年SQLite命令行工具的人,这次给它补上了数据库迁移功能——却主动砍掉了回滚。这是sqlite-utils 4.0里最反常的一个决定,也是这次发布真正值得聊的地方。
7月7日,Simon Willison发布了sqlite-utils 4.0,这是该项目第124次发布,也是自2020年11月3.0以来的首次主版本升级。三大新功能:数据库迁移、通过db.atomic()实现的嵌套事务、复合外键支持。听起来是常规的功能补齐,但拆开看,这次升级藏着一个老牌维护者对“该做什么、不该做什么”的清醒判断。
迁移是怎么绕过SQLite限制的
SQLite原生的ALTER TABLE长期有限制,历史上不能直接删列、改类型、调整列顺序。sqlite-utils的解法是官方文档推荐的老办法:建一张临时表,把数据搬过去,删掉旧表,再把临时表改名顶替。table.transform()方法就是这套流程的封装。
迁移功能建立在这套机制之上。写一个migrations.py文件,用@migrations()装饰器定义一步步的变更——建表、加列、改类型,sqlite-utils会自动记录哪些迁移已经跑过,存在一张叫_sqlite_migrations的表里,字段是migration_set、name、applied_at。运行sqlite-utils migrate,它会扫描目录里的migrations.py,把没跑过的迁移依次执行。
嵌套事务是配套设施。SQLite支持Savepoints,db.atomic()可以嵌套调用,一次迁移里如果某一步失败,前面的改动能整体回滚,不至于把数据库改成半吊子状态。这个设计其实解决了sqlite-utils自己长期的老问题——Simon承认早年设计这个库时,自己对SQLite事务的理解并不透彻。
三年beta,一年打磨,才敢叫4.0
迁移这套设计不是这次现做的。三年前它是一个独立的beta包,叫sqlite-migrate,一直没转正。4.0的alpha版本早在2025年5月就公开了,一年多以后才正式发布——这中间的时间差,说明这次升级不是临时起意,而是拖了很久、改了很久才敢定型的东西。
对已经在用sqlite-migrate的用户,升级成本很低:迁移表结构完全没变,只需要把import从sqlite_migrate换成sqlite_utils。Simon甚至给sqlite-migrate包本身发了最后一版,让它变成一个空壳,内部直接转发sqlite_utils.Migrations,老代码不用改。
复合外键支持则是另一条线,拖到4.0才补齐,原因很直接:这类改动一旦加进去就是不兼容变更,只能攒到大版本一起放出来。
站在Django和Alembic旁边看,才看得清它的定位
2008年首届DjangoCon上,有一场“Schema Evolution”专题讨论,Simon和Andrew Godwin、Russ Keith-Magee三人各自展示了自己的迁移方案——Simon那套叫dmigrations。18年后,Andrew做的Django Migrations成了公认的标杆:能从模型定义自动生成迁移,还能回滚到任意历史版本。
sqlite-utils 4.0的迁移功能,刻意没有走这条路。
sqlite-utils的定位一直是程序化建表,不是ORM,这决定了它天然没有“模型”可供推导迁移——这不是能力不够,是起点不同。就连Alembic自己的维护者也在公开讨论里承认,“数据迁移”(区别于结构迁移)至今仍是没彻底解决的难题,SQLite受限的ALTER TABLE同样逼着Alembic走建临时表的老路。可见这不是sqlite-utils一家的短板,是整个SQLite生态共同要面对的地基问题。
不做回滚,是这次最锋利的一刀
Simon的原话是:回滚“很少被用到”,想恢复就直接复制一份数据库文件。这话听着轻描淡写,但放在Django、Alembic都把回滚当核心能力的坐标系里,是个相当鲜明的态度。
- 结论.SQLite是单文件数据库,备份成本极低,这让“不做回滚”这个决定比听起来合理得多。
- 风险.习惯了正式迁移框架、依赖版本图谱和自动回滚的团队,迁移到sqlite-utils之后要自己承担这部分手工纪律。
敢明说“这个功能我不做”,比堆功能表更考验判断力。
孔子说“过犹不及”,工具设计也是这个道理。功能堆得越多,维护成本和心智负担越重;sqlite-utils这次的选择,是把复杂度死死摁在“轻量脚本工具”这个定位里,不往ORM、不往完整迁移框架去够。
谁该升级,谁该再等等
已经在用sqlite-migrate的项目,升级几乎零成本,换个import就行。用sqlite-utils做程序化建表、又一直在Alembic和裸写SQL之间摇摆的开发者,现在多了一个不需要ORM的轻量选项。习惯了Django Migrations那套完整版本管理和团队协作流程的人,大概率不会为这个换车——它解决的不是同一类问题。
接下来值得盯的,是db.atomic()和复合外键在实际项目里跑一段时间后会不会冒出新的边界情况。功能好不好用,从来不是发布会那天定的,是几个月后线上事故报告里写出来的。
