sqlite-utils 4.0:新增数据库迁移与嵌套事务
sqlite-utils 4.0 发布,这是该项目的第 124 次发布,也是自 2020 年 11 月推出 3.0 以来首次升级主版本号。
本次更新带来三项主要功能:数据库结构迁移、通过 db.atomic() 实现的嵌套事务,以及复合外键支持。此外,版本中还包含一些规模较小但需要注意的不兼容变更,升级前应检查兼容性。
数据库结构迁移如何工作
数据库结构迁移将表结构的演进定义为一系列操作,同时记录哪些操作已经执行,并应用尚未执行的迁移。
sqlite-utils 使用 Python 文件定义迁移。下面的例子分三步完成表结构变更:先创建 creatures 表,再添加 weight 列,最后修改两个列的类型。
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}
)
其中,table.transform() 提供了 SQLite ALTER TABLE 语句未直接支持的一些表结构变更能力。其实现方式是:创建采用新结构的临时表、复制数据、删除旧表,再将临时表重命名。
执行迁移与查看状态
将上述代码保存为 migrations.py,然后针对一个新数据库执行:
uvx sqlite-utils migrate data.db migrations.py
查看数据库结构:
uvx sqlite-utils schema data.db
执行全部三项迁移后,业务表结构为:
CREATE TABLE "creatures" (
"id" INTEGER PRIMARY KEY,
"name" TEXT,
"species" INTEGER,
"weight" TEXT
);
数据库中还会创建 _sqlite_migrations 表,用于记录已执行的迁移函数,包含迁移集合、函数名称和执行时间等信息。
要同时查看已执行和待执行的迁移,可以运行:
uvx sqlite-utils migrate data.db migrations.py --list
如果不指定迁移文件:
sqlite-utils migrate data.db
命令会扫描当前目录及其子目录中的 migrations.py 文件,并应用其中找到的 Migrations() 实例。
也可以在 Python 中调用:
migrations.apply(db)
这种方式适合需要跨版本管理自身数据库结构的工具。
设计取舍:不自动生成迁移,也不内置回滚
与 Django 的迁移系统相比,sqlite-utils 采用了更简单的设计。
- 迁移需要自行编写:sqlite-utils 鼓励通过程序代码创建表,而不是通过 ORM 模型定义,因此没有用于自动生成迁移的模型定义。
- 不提供内置回滚功能:对于 SQLite 项目,可以在执行迁移前复制数据库文件,以便需要时恢复。
因此,使用这套机制时,需要明确区分“记录并执行结构变更”和“自动推导变更、生成逆向操作”这两类能力。
从 sqlite-migrate 过渡
这套迁移设计此前以独立包 sqlite-migrate 的形式存在,已有约三年历史,但一直未脱离测试版阶段。现在,它被整合进 sqlite-utils,成为默认可用的功能。
sqlite-migrate 的最后一次发布改为依赖 sqlite-utils>=4,并直接导出同一个 Migrations 类:
from sqlite_utils import Migrations
__all__ = ["Migrations"]
已有依赖 sqlite-migrate 的项目应能继续运行,无须修改。
另外两项主要更新
嵌套事务通过新的 db.atomic() 上下文管理器提供,同时该版本改进了库中多处事务处理行为。将迁移纳入核心库,也是此次重新梳理事务机制的重要契机。
复合外键支持也是 4.0 的主要新增能力之一。
对于准备升级的项目,除了尝试迁移功能,还应关注不兼容变更,并在执行数据库结构变更前做好备份。