没有迁移的 SQLite,像一套只供参观的样板间:第一天看着敞亮,第二天一住人就得凿墙走线。我判断得更直白:SQLite 没迁移,才只是玩具。sqlite-utils 4.0 把数据库结构迁移装进来,等于第一次给这套房子配上施工图。

这里说的 SQLite,是那种常嵌在应用里、跟一个数据库文件绑在一起的本地数据库。2026 年 7 月 7 日,sqlite-utils 4.0 的更新日志很平静地写下了一件并不平静的事:加入了数据库结构迁移。这不是多了一个按钮,而是补上了一种时间观。过去不少人误会了 SQLite,以为它小,所以可以随便;以为它轻,所以不必郑重。错。数据库成不成年,从来不看体重,看它能不能接住昨天。

所谓数据库结构迁移,说白了,就是让旧库按步骤长成新库:今天加一列,明天建一张表,后天补一个索引,每一步都记账,下次只做没做过的那几步。sqlite-utils 4.0 把这套账本放进了工具里:每次变更会记进 _sqlite_migrations 表;重复执行,不会把做过的活再拆一遍;每一步默认包在事务里,出错就整步回滚;你还能先列清待办,再决定停在哪一步。一个工具开始关心“已经做过什么”和“做到哪一步停”,它就不再是玩具。

周一晚上 11 点,做社区团购小程序的阿哲在 users 表里手敲加了个 phone_verified 字段。本机一跑,风平浪静;第二天把新版本发给测试群,三个老用户的数据库还是旧结构,后台第一条红字就是 no such column。这类错误最挖苦人的地方在于:功能明明是新的,失败却死在历史上。不是代码不努力,是数据库被当成了一次性道具。

没有迁移的数据库,不是地基,只是开发者当天的心情。

再看一个更寒酸、也更常见的场景。一个四人内容团队用 SQLite 存选题、稿件和发布时间。最初只有 articles 一张表,后来要做协作,加了 statuseditor_idpublished_at,又补了索引。新同事从网盘拖下一份半年前的数据库,脚本照新版逻辑跑,周报里少了 17 篇稿。谁也不会第一时间怀疑“数据库结构分叉”这种听上去像学术病名的东西,大家只会互相问:是不是你没同步?没有迁移,所谓同一个库,不过是一群长得一样的平行宇宙。

所谓玩具,不是看文件有多小,是看它能不能承受昨天。

第三个场景最像今天。一个做本地 AI 笔记工具的小团队,把聊天记录、附件路径、搜索索引都放在 SQLite 里。新版本要加全文检索表、附件表、会话摘要。新安装的用户一切丝滑,老用户升级后搜索框像哑巴,摘要页像失忆。客服最后祭出那句软件界的白旗:你重装一下。许多所谓兼容性问题,翻译成人话,其实就是:我们没有认真对待你的旧数据。

能写入数据,只证明你会开张;能让旧数据活着升级,才算会经营。

数据库最怕的不是大,而是失忆:改过什么,为什么改,改到哪一步,全没人记得。

重装不是解决方案,重装只是没有迁移系统的软件在给用户赔礼。

所以我才说,SQLite 没迁移,才只是玩具。注意,玩具的特征不是轻,不是小,不是单文件;玩具的特征是它默认世界没有历史。孩子搭积木,推倒了可以重来。产品不行。产品一旦发出去,数据库就不是你桌上的实验瓶,而是别人手里的旧账本、旧房子、旧生活。你今天改一列,明天拆一表,后天补一个约束,真正要回答的问题不是“新版本能不能跑”,而是“昨天留下的那些东西,今天怎么活下去”。

这正是 sqlite-utils 4.0 值钱的地方。它本来是个操作 SQLite 的利落工具,现在终于在最该认真的地方变得认真:不再只帮你创建世界,也帮你修改世界;不再只会开荒,也开始负责拆迁、补线、修路,而且每一步留档。软件的成熟,从来不是先学会扩容,而是先学会记账;不是先学会跑得快,而是先学会别把昨天撞死。

SQLite 从来不小。小的,是过去很多人对它的用法。没有迁移时,拿它做产品,不过是在拿样板间当家。


别人聊 AI,我们测 AI——每个结论都能下载原始数据自己复算。 🔗 官网 👉 https://crawdpad.com