sqlite-utils 4.0, now with database schema migrations
TL;DR · AI 摘要
sqlite-utils 4.0新增数据库迁移功能,支持通过Python脚本管理SQLite模式变更,提升数据库维护效率。
核心要点
- 使用table.transform()方法实现SQLite不支持的复杂表结构变更
- 迁移记录存储在_sqlite_migrations表中,支持迁移状态追踪
- 通过uvx命令行工具执行迁移,支持--list参数查看迁移状态
结构提纲
按章节快速跳转。
介绍sqlite-utils 4.0主要更新内容及版本演进历程
解释数据库迁移的定义及sqlite-utils的实现方式
详细说明通过临时表实现schema变更的技术方案
展示包含表创建、字段添加和类型修改的完整迁移脚本
说明使用uvx命令执行迁移及查看schema的完整操作流程
介绍嵌套事务和复合外键支持等附加功能
思维导图
用一张图看清主题之间的关系。
查看大纲文本(无障碍 / 无 JS 友好)
- sqlite-utils 4.0新特性
- 数据库迁移
- 迁移文件定义
- _sqlite_migrations表
- uvx命令执行
- 嵌套事务
- db.atomic()方法
- 复合外键支持
金句 / Highlights
值得收藏与分享的关键句。
table.transform()实现SQLite不支持的alter table操作,通过创建临时表实现schema变更
迁移记录存储在_sqlite_migrations表,包含迁移名称、应用时间和状态信息
使用uvx sqlite-utils migrate命令可执行迁移,--list参数可查看已应用和待执行迁移
sqlite-utils 4.0,新增数据库模式迁移功能
Simon Willison 的博客
订阅
#smallhead
由以下公司赞助:
Sonar — Gartner 最近将 Sonar 评为 2026 年 Gartner 技术债务管理工具魔力象限™ 的领导者。阅读报告,了解如何衡量和解决代码库中的技术债务。
获取报告
sqlite-utils 4.0,新增数据库模式迁移功能
2026 年 7 月 7 日
今天早上我发布了 sqlite-utils 4.0,这是该项目的第 124 次发布,自 2020 年 11 月 3.0 版本以来的首次重大版本升级。除了若干重要但具有破坏性的变更(详见此升级指南),该版本新增了三项主要功能:数据库迁移、嵌套事务(通过新的 db.atomic() 方法实现),以及复合外键支持。
#### 使用 sqlite-utils 实现数据库模式迁移
模式迁移定义了对 SQLite 数据库的一系列变更,并提供了一种跟踪已应用迁移和处理待处理迁移的机制。
迁移通过 sqlite-utils Python 库定义在 Python 文件中,该库包含强大的 table.transform() 方法,提供了 SQLite 的 ALTER TABLE 语句不支持的增强表结构修改能力。
(table.transform() 实现了 SQLite 官方文档推荐的模式:创建一个具有新结构的临时表,复制数据,然后删除旧表并重命名临时表。)
以下是一个迁移文件示例,它首先创建一个名为 creatures 的表,然后在第二步添加一个额外列,最后在第三步修改两个列的数据类型:
from
sqlite_utils
import
Migrations
migrations
=
Migrations
(
"creatures"
)
@
migrations
()
def
create_table
(
db
):
db
[
"creatures"
].
create
(
{
"id"
:
int
,
"name"
:
str
,
"species"
:
str
},
pk
=
"id"
,
)
@
migrations
()
def
add_weight
(
db
):
db
[
"creatures"
].
add_column
(
"weight"
,
float
)
@
migrations
()
def
change_column_types
(
db
):
db
[
"creatures"
].
transform
(
types
=
{
"species"
:
int
,
"weight"
:
str
})将上述内容保存为 migrations.py 并按如下方式运行:
uvx sqlite-utils migrate data.db migrations.py然后检查数据库模式:
uvx sqlite-utils schema data.db你将看到以下 SQL:
CREATE
TABLE
"
_sqlite_migrations
" (
"
id
"
INTEGER
PRIMARY KEY
,
"
migration_set
"
TEXT
,
"
name
"
TEXT
,
"
applied_at
"
TEXT
);
CREATE
UNIQUE INDEX
"
idx__sqlite_migrations_migration_set_name
"
ON
"
_sqlite_migrations
"
(
"
migration_set
"
,
"
name
"
);
CREATE
TABLE
"
creatures
" (
"
id
"
INTEGER
PRIMARY KEY
,
"
name
"
TEXT
,
"
species
"
INTEGER
,
"
weight
"
TEXT
);_sqlite_migrations 表用于跟踪已执行的迁移函数。上面的 creatures 表是应用所有三个迁移后的最终模式。
要查看已应用和待处理的迁移列表,运行:
uvx sqlite-utils migrate data.db migrations.py --list输出结果:
Migrations for: creatures
Applied:
create_table - 2026-07-07 17:58:41.360051+00:00
add_weight - 2026-07-07 17:58:41.360608+00:00
change_column_types - 2026-07-07 18:01:15.802000+00:00
Pending:
(none)如果您未指定迁移文件,sqlite-utils migrate data.db 命令会扫描当前目录及其子目录中名为 migrations.py 的文件,并应用其中找到的任何 Migrations() 实例。
您也可以通过 migrations.apply(db) 方法从 Python 代码中执行迁移,这对于构建需要管理多版本数据库模式的工具非常有用。我自己的 LLM 工具已经使用这种模式的变体多年,如 llm/embeddings_migrations.py 所示。
#### 已有技术
我最喜欢的这种模式实现仍然是 Django 的 Migrations,由 Andrew Godwin 基于他早期的 South 项目开发。有趣的是:2008 年在首届 DjangoCon 上,Andrew、Russ Keith-Magee 和我曾在 Schema Evolution 面板上展示过我们针对 Django 的模式迁移的相互竞争方案!我的方案当时叫做 dmigrations,是在伦敦 Global Radio 的团队中开发的。
Django 的迁移可以从模型定义自动生成,并包含回滚到早期版本的功能。而 sqlite-utils 的方法刻意保持简单:与 Django 不同,sqlite-utils 鼓励通过编程方式创建表而不是使用模型定义 ORM,因此我们没有可以用来自动生成迁移的工具。
我决定跳过回滚功能,因为在我的经验中,这个功能很少被使用。对于 SQLite 项目,实现回滚的一种简单方法是在应用迁移之前创建数据库文件的副本!
#### 从 sqlite-migrate 迁移
sqlite-utils 的迁移设计已有三年历史——我最初将其作为名为 sqlite-migrate 的独立包发布,但该包从未真正超越 beta 版本。
我现在已在足够多的地方使用了这个包,对设计充满信心,因此决定将其作为 sqlite-utils 的一个功能进行推广,以便在不断增长的 sqlite-utils/Datasette/LLM 生态系统中默认提供给所有其他工具。
我发布了 sqlite-migrate 的最后一次版本,将其依赖项改为 sqlite-utils>=4,并将 __init__.py 文件替换为以下内容:
from sqlite_utils import Migrations
__all__ = ["Migrations"]任何依赖 sqlite-migrate 的现有项目在不进行任何更改的情况下应继续正常工作。
#### sqlite-utils 4.0 的其他所有内容
以下是此版本的发布说明,包含一些内联注释:
4.0 版本包含一些向后不兼容的次要修复(因此主版本号升级)并引入了三个重大新功能:数据库迁移,提供了一种结构化机制来随着时间推移演进项目的模式。(#752)
我认为迁移是这个版本的标志性新功能,因此撰写了这篇博文。
通过
db.atomic()支持嵌套事务,以及对库中事务处理方式的多项改进。(#755)
sqlite-utils 一直与数据库事务有着复杂的关系,部分原因是我早在 2018 年设计这个库时,对 SQLite 本身的事务机制还缺乏深入理解。
将迁移功能添加到核心库后,我下定决心解决这个问题,因为事务可以使迁移系统更加安全且易于理解。
最终我围绕 db.atomic() 上下文管理器构建了这个功能,其结构如下:
with
db
.
atomic
():
db
.
table
(
"dogs"
).
insert
({
"id"
:
1
,
"name"
:
"Cleo"
},
pk
=
"id"
)
db
.
table
(
"dogs"
).
insert
({
"id"
:
2
,
"name"
:
"Pancakes"
})SQLite 支持保存点,因此 db.atomic() 可以嵌套使用,以在事务内部执行事务。这非常方便!
支持复合外键,包括通过 table.foreign_keys 的创建、转换和内省。(#594)
这源于我要求一个编码代理审查所有开放问题和 PR,找出应包含在 4.0 版本中的内容,因为如果之后添加这些功能将构成重大变更,而它正确识别出复合外键正是这种类型的功能。
我首先对 table.foreign_keys 内省方法进行了破坏性变更,然后决定看看 Claude Fable 5 是否能处理将复合外键创建集成到库中的更复杂任务。它帮助设计的 API 与库其余部分的工作方式完全一致。
其他显著变更包括:Upserts 现在使用 SQLite 的 INSERT ... ON CONFLICT ... DO UPDATE SET 语法,可自动检测现有表的主键,并拒绝缺少必需主键值的记录。(#652)
正是这个变更促使我首次考虑进行破坏性变更的 4.0 版本升级。我构建了这个功能来支持 sqlite-chronicle,它使用触发器来跟踪表中已插入、更新或删除的行。
db.query() 现在会立即执行,并拒绝不返回行的语句;使用 db.execute() 进行写入和 DDL 操作。
这可能是最具破坏性的变更——我不得不更新自己代码中的几个地方,将 db.query() 切换为 db.execute()。
CSV 和 TSV 导入现在默认检测列类型,而插入现有表时会保留这些表的列类型。(#679)
sqlite-utils 的 insert data.db creatures creatures.csv --detect-types 标志是后来添加的,用于根据 CSV 中的数据自动检测列类型(文本、整数、实数)。这应该是默认行为,发布 4.0 版本意味着我可以实现这一点。
table.extract() 和 extracts= 现在不会为全空值创建查找表记录。(#186)
这个版本解决的最老问题——底层的错误是由我于 2020 年 10 月打开的。
有关向后不兼容变更的详细信息,请参阅《从 3.x 升级到 4.0》。4.0 预发布周期内发布的功能和修复的详细版本说明可在 4.0a0、4.0a1、4.0rc1、4.0rc2、4.0rc3 和 4.0rc4 中找到。
升级指南完全由 Claude Fable 5、Claude Opus 4.8 和 GPT-5.5 编写。版本说明也是如此。
这就是我逐渐习惯将此类文档外包给机器人的类型。它不需要说服任何人,也不需要表达任何观点——它的任务是尽可能准确和详细。我仔细审查了版本说明,可以确认它们是准确且全面的。
#### Claude Fable 5 提供了很大帮助
我一年多前发布了 sqlite-utils 4.0 的第一个 alpha 版本。由于要追踪和清理重大版本号允许我承担的众多其他次要设计缺陷所需的工作量,我一直在拖延稳定版本的发布。
Claude Fable 5(以及 Opus 4.8 和 GPT-5.5 在较小程度上)的帮助让我获得了克服惯性所需的推动力,使我能够充分利用在该库上投入的时间。
Fable 在 API 设计方面有非常好的品味,如果你给它一个更开放的目标,它会坚持不懈地主动行动。我最成功的提示是对我认为是最后一个发布候选版本的代码库发起的审查任务:
审查 main 分支自上一个 3.x 版本标签以来的更改 - 我即将将其作为 sqlite-utils 4.0 发布,这是一个稳定的版本,承诺在很长一段时间内不会出现向后不兼容的修复。审查变更日志和升级指南,并编写一些脚本尝试使用 v4 的所有新功能 - 保存这些脚本但不要提交
我在 Codex Desktop 的 GPT-5.5 xhigh 和 Claude Code 的 Fable 5 上尝试了这个任务。
GPT-5.5 编写了 5 个 Python 脚本,但没有发现特别有趣的内容——其最终报告在这里。
Fable 5 编写了 12 个脚本,在其报告中识别出 4 个发布阻碍和 10 个其他问题,并构建了一个整洁的综合复现脚本,运行后输出如下:
=== 1. 失败的 db.execute() 写入操作会留下一个隐式事务开启 ===
写入失败后仍在事务中: True
BUG: 连接关闭时表 'other' 被静默删除
=== 2. 领先的 ';' 绕过了 query() 的第一个标记扫描器 ===
BUG: 抛出 OperationalError: 没有名为 sqlite_utils_query 的保存点
BUG: 回滚后行仍然存在(计数=1)
=== 3. 通过 query() 拒绝的写入 PRAGMA 仍然生效 ===
BUG: 在 'rejected' 语句后 user_version=5(文档说明无影响)
=== 4. 隐式复合外键按表顺序而非主键顺序解析主键列 ===
BUG: other_columns 报告为 ('b', 'a'),应为 ('a', 'b')
BUG: 有效数据的转换引发 IntegrityError: 外键约束失败
=== 5. ForeignKey(现在是一个数据类)不再可哈希 ===
BUG: 无法将 'sqlite_utils.db.ForeignKey' 用作集合元素(不可哈希类型: 'ForeignKey')
=== 6. foreign_keys= 中混合使用 ForeignKey 对象和元组被拒绝 ===
BUG: foreign_keys= 应该是元组列表
=== 7. 将 CSV 插入现有表会转换其列类型 ===
BUG: 现有 zip '01234' 现在变为 1234(列类型: int)
=== 8. insert(pk=, alter=True) 回归: alter 运行前出现 InvalidColumns ===
BUG: InvalidColumns: 表 t 的主键列 ['id'] 无效(表列 ['a'])
=== 9. migrate --stop-before 已经应用的迁移会应用所有内容 ===
BUG: 尽管指定 --stop-before m1(m1 已经应用),m2 仍然被应用
=== 10. ensure_autocommit_on() 静默提交了打开的事务 ===
BUG: 回滚后行仍然存在(计数=1)- 事务已被提交我几乎同意所有这些问题。这是包含 16 次提交的 PR,我们依次解决了这些问题。
毫无疑问,有了最新前沿模型的帮助,sqlite-utils 4.0 的质量远高于我独自开发时的版本。
发布
于下午 7:32 · 关注我
Mastodon
,
Bluesky
或
订阅我的通讯
更近的文章
- sqlite-utils 4.0rc2,主要由 Claude Fable 编写(约 149.25 美元)- 2026 年 7 月 5 日
- 让你的代理使用 shot-scraper 视频录制其工作视频演示 - 2026 年 6 月 30 日
#primary
这是 sqlite-utils 4.0,现在由 Simon Willison 提供数据库模式迁移功能,发布于 2026 年 7 月 7 日。
系列文章:sqlite-utils 新特性
- sqlite-utils 4.0a1 引入了若干(次要的)向后不兼容的更改 - 2025年11月24日 下午2:52
- sqlite-utils 4.0rc1 新增了迁移功能和嵌套事务支持 - 2026年6月21日 晚上11:35
- sqlite-utils 4.0rc2(主要由 Claude Fable 编写,约149.25美元)- 2026年7月5日 凌晨1点
- sqlite-utils 4.0 现已支持数据库模式迁移 - 2026年7月7日 晚上7:32
schema-migrations 26 projects 545 sqlite 476 ai 2,104 sqlite-utils 231 annotated-release-notes 59 generative-ai 1,861 llms 1,828 ai-assisted-programming 392 anthropic 306 claude 289 agentic-engineering 56 claude-mythos-fable 25
上一篇:sqlite-utils 4.0rc2(主要由 Claude Fable 编写,约149.25美元)
月度简报
每月赞助我10美元,即可获取精选的LLM领域重要进展邮件摘要。 让我少发点邮件,你来付钱! 赞助并订阅
#secondary #wrapper
- 披露信息
- 制作说明
- ©
- 2002
- 2003
- 2004
- 2005
- 2006
- 2007
- 2008
- 2009
- 2010
- 2011
- 2012
- 2013
- 2014
- 2015
- 2016
- 2017
- 2018
- 2019
- 2020
- 2021
- 2022
- 2023
- 2024
- 2025
- 2026