sqlite-utils 4.0rc1 增加迁移和嵌套事务
Simon Willison··作者 Simon Willison
关键信息
迁移系统是早先 `sqlite-migrate` 包的一个小幅移植版本,而且刻意保持简单:它不提供反向迁移。事务能力基于 SQLite 的 savepoint,这是 SQLite 用来模拟嵌套事务的机制。
资讯摘要
sqlite-utils 是 Simon Willison 推出的 Python 库和命令行工具,用来处理 SQLite 数据库。它建立在 Python 自带的 `sqlite3` 模块之上,已经提供了表结构转换、从 JSON 自动建表等更高层的功能。本文宣布了 sqlite-utils 4.0rc1,这是 v4 系列的首个候选发布版。作者表示,这次主版本号升级意味着存在一些轻微的不兼容改动,因此希望用户在正式版前先试用并反馈。
这个版本最重要的新特性之一是数据库迁移功能,而且它已经直接集成进 sqlite-utils。这个迁移系统并不是全新实现,而是对早先 `sqlite-migrate` 包做了轻微修改后的移植版,作者认为它已经在像 LLM 这样的项目中证明了稳定性。新 API 允许用户在 `migrations.py` 文件里按顺序定义迁移函数,并可以通过 Python 代码或 `sqlite-utils migrate` 命令执行。这个系统刻意保持小而简单,不提供反向迁移,所以如果迁移出错,通常需要通过新增一条迁移来修正,而不是回滚。
资讯正文
sqlite-utils 是我用于处理 SQLite 数据库的 Python 库和 CLI 工具的组合。它在 Python 自带的 `sqlite3` 包之上提供了一套广泛的高级操作,包括支持复杂的表转换、根据 JSON 数据自动创建表,以及更多功能。
我发布了 `sqlite-utils 4.0rc1`,这是 sqlite-utils v4 的首个候选发布版。主版本号的提升意味着存在一些(轻微的)向后不兼容变更,所以在我将其推进为稳定版之前,我很希望大家先试用一下。
**新功能:迁移**
与之前的 4.0 alpha 版本相比,这个 RC 有两个重要的新功能。
第一个是对数据库迁移的支持。这并不是一个完全全新的实现——它是我几年前发布的 `sqlite-migrate` 包经过稍作修改后的移植版本。我认为这个包经过了时间的验证,因此现在我准备把它直接捆绑进 `sqlite-utils`。
下面是 `migrations.py` 文件中一组迁移的样子:
```python
from sqlite_utils import Database, 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)
```
这定义了两项迁移:一项创建 `creatures` 表,另一项为其添加一个列。
然后你可以用 Python 来运行这些迁移:
```python
db = Database("creatures.db")
```
<span class="pl-s1">migrations</span>.<span class="pl-c1">apply</span>(<span class="pl-s1">db</span>)</pre>
或者使用命令行 <code>migrate</code> 命令:
<div class="highlight highlight-source-shell"><pre>sqlite-utils migrate creatures.db migrations.py</pre></div>
这个系统故意设计得很小:它不提供反向迁移,所以你犯下的任何错误,都应该通过部署一条新的迁移来撤销。
它的前身已经被 <a href="https://llm.datasette.io/">LLM</a> 以及其他各种项目使用了好几年,所以我有信心这个设计是稳定的,而且运行良好。
新的 migrations 功能<a href="https://sqlite-utils.datasette.io/en/latest/migrations.html">文档在这里</a>。
<h4 id="new-feature-db-atomic-transactions">新功能:db.atomic() 事务</h4>
这个功能远没有 migrations 那么经过充分使用,因此更值得测试者多加关注。
此前,<code>sqlite-utils</code> 主要把事务管理交给用户自己处理,通过 <code>with db.conn:</code> 结构直接复用 <code>sqlite3</code> 机制。
SQLite 以 savepoint 的形式支持嵌套事务,所以我想要一种抽象,能让这类事务尽可能容易使用。
我借用了 Django 和 Peewee 中的 “atomic” 这个术语。下面是新的 API 长什么样:
<pre><span class="pl-k">with</span> <span class="pl-s1">db</span>.<span class="pl-c1">atomic</span>():
<span class="pl-s1">db</span>.<span class="pl-c1">table</span>(<span class="pl-s">"dogs"</span>).<span class="pl-c1">insert</span>({<span class="pl-s">"id"</span>: <span class="pl-c1">1</span>, <span class="pl-s">"name"</span>: <span class="pl-s">"Cleo"</span>}, <span class="pl-s1">pk</span><span class="pl-c1">=</span><span class="pl-s">"id"</span>)
<span class="pl-k">try</span>:
<span class="pl-k">with</span> <span class="pl-s1">db</span>.<span class="pl-c1">atomic</span>():
<span class="pl-s1">db</span>.<span class="pl-c1">table</span>(<span class="pl-s">"dogs"</span>).<span class="pl-c1">insert</span>({<span class="pl-s">"id"</span>: <span class="pl-c1">2</span>, <span class="pl-s">"name"</span>: <span class="pl-s">"Pancakes"</span>})
<span class="pl-k">raise</span> <span class="pl-en">ValueError</span>(<span class="pl-s">"skip this one"</span>)
<span class="pl-k">except</span> <span class="pl-v">ValueError</span>:
<span class="pl-k">pass</span>
<span class="pl-s1">db</span>.<span class="pl-c1">table</span>(<span class="pl-s">"dogs"</span>).<span class="pl-c1">insert</span>({<span class="pl-s">"id"</span>: <span class="pl-c1">3</span>, <span class="pl-s">"name"</span>: <span class="pl-s">"Marnie"</span>})</pre>
更多细节见<a href="https://sqlite-utils.datasette.io/en/latest/python-api.html#transactions-with-db-atomic">文档</a>。
<h4 id="backwards-incompatible-changes">不向后兼容的变更</h4>
v4 中不向后兼容的变更已在 alpha 发行说明中描述。对于 <a href="https://sqlite-utils.datasette.io/en/latest/changelog.html#a0-2025-05-08">4.0a0</a>:
<blockquote>
<ul>
<li>现在,在所有高于 3.23.1 的 SQLite 版本上,upsert 操作都会使用 SQLite 的 <code>INSERT ... ON CONFLICT SET</code> 语法。对于依赖此前先执行 <code>INSERT OR IGNORE</code> 再执行 <code>UPDATE</code> 行为的应用来说,这是一项非常轻微的破坏性变更。(<a href="https://github.com/simonw/sqlite-utils/issues/652">#652</a>)</li>
<li>Python 库用户可以通过在 <code>Database()</code> 构造函数中传入 <code>use_old_upsert=True</code> 来选择启用旧实现,参见 <a href="https://sqlite-utils.datasette.io/en/latest/python-api.html#python-api-old-upsert">使用 INSERT OR IGNORE 的替代 upsert</a>。</li>
<li>已放弃对 Python 3.8 的支持,新增对 Python 3.13 的支持。(<a href="https://github.com/simonw/sqlite-utils/issues/646">#646</a>)</li>
<li>
<code>sqlite-utils tui</code> 现在由 <a href="https://github.com/simonw/sqlite-utils-tui">sqlite-utils-tui</a> 插件提供。(<a href="https://github.com/simonw/sqlite-utils/issues/648">#648</a>)</li>
<li>测试套件现在也会在 SQLite 3.23.1 上运行,这是在新增 <code>INSERT ... ON CONFLICT SET</code> 语法之前的最后一个版本(发布于 2018-04-10)。(<a href="https://github.com/simonw/sqlite-utils/issues/654">#654</a>)</li>
</ul>
</blockquote>
<p>而对于 <a href="https://sqlite-utils.datasette.io/en/latest/changelog.html#a1-2025-11-23">4.0a1</a>:</p>
<blockquote>
<ul>
<li>
<strong>破坏性变更</strong>:<code>db.table(table_name)</code> 方法现在只能用于表。若要访问 SQL 视图,请改用 <code>db.view(view_name)</code>。(<a href="https://github.com/simonw/sqlite-utils/issues/657">#657</a>)</li>
<li><code>table.insert_all()</code> 和 <code>table.upsert_all()</code> 方法现在除了字典之外,还可以接受由列表或元组组成的迭代器。第一项应为列名的列表/元组。详情请参见 <a href="https://sqlite-utils.datasette.io/en/latest/python-api.html#python-api-insert-lists">从列表或元组迭代器插入数据</a>。(<a href="https://github.com/simonw/sqlite-utils/issues/672">#672</a>)</li>
<li>
<strong>破坏性变更</strong>:默认的浮点列类型已从 <code>FLOAT</code> 改为 <code>REAL</code>,这是 SQLite 中表示浮点值的正确类型。这会影响插入数据时自动检测到的列。(<a href="https://github.com/simonw/sqlite-utils/issues/645">#645</a>)</li>
<li>现在打包时使用 <code>pyproject.toml</code> 取代 <code>setup.py</code>。(<a href="https://github.com/simonw/sqlite-utils/issues/675">#675</a>)</li>
<li>Python API 中的表现在能够更好地记住其最初创建时的主键以及其他模式细节。(<a href="https://github.com/simonw/sqlite-utils/issues/655">#655</a>)</li>
<li>
<strong>破坏性变更</strong>:<code>table.convert()</code> 和 <code>sqlite-utils convert</code> 机制不再跳过求值结果为 <code>False</code> 的值。此前需要使用 <code>--skip-false</code> 选项,现在该选项已被移除。(<a href="https://github.com/simonw/sqlite-utils/issues/542">#542</a>)</li>
<li>
<strong>破坏性变更</strong>:这个库创建的表现在会在 schema 中把表名和列名用 <code>"双引号"</code> 包起来。此前使用的是 <code>[方括号]</code>。(<a href="https://github.com/simonw/sqlite-utils/issues/677">#677</a>)</li>
<li><code>--functions</code> 命令行参数现在除了接受一段 Python 代码字符串之外,还可以接受指向 Python 文件的路径。它现在也可以重复指定多次。(<a href="https://github.com/simonw/sqlite-utils/issues/659">#659</a>)</li>
<li>
<strong>破坏性变更:</strong>在导入 CSV 或 TSV 数据时,<code>insert</code> 和 <code>upsert</code> 命令现在默认启用类型检测。此前,除非传入 <code>--detect-types</code> 标志,否则所有列都会被视为 <code>TEXT</code>。使用新的 <code>--no-detect-types</code> 标志可以恢复旧行为。<code>SQLITE_UTILS_DETECT_TYPES</code> 环境变量已被移除。(<a href="https://github.com/simonw/sqlite-utils/issues/679">#679</a>)</li>
</ul>
</blockquote>
<h4 id="try-it-out">试试看</h4>
<p>你可以这样安装这个新的 RC 版本:</p>
<div class="highlight highlight-source-shell"><pre>pip install sqlite-utils==4.0rc1</pre></div>
<p>或者也可以用 <code>uvx</code> 直接试用 CLI 版本,如下:</p>
<div class="highlight highlight-source-shell"><pre>uvx --with sqlite-utils==4.0rc1 sqlite-utils --help</pre></div>
<p>欢迎到 <a href="https://discord.gg/Ass7bCAMDw">sqlite-utils Discord 频道</a> 和我们聊聊,或者在 <a href="https://github.com/simonw/sqlite-utils/issues">GitHub Issues</a> 里提交任何 bug。</p>
<p>标签:<a href="https://simonwillison.net/tags/migrations">migrations</a>,<a href="https://simonwillison.net/tags/projects">projects</a>,<a href="https://simonwillison.net/tags/sqlite">sqlite</a>,<a href="https://simonwillison.net/tags/sqlite-utils">sqlite-utils</a>,<a href="https://simonwillison.net/tags/annotated-release-notes">annotated-release-notes</a></p>
来源与参考
收录于 2026-06-23