登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Alembic 在 SQLite 中怎么批量修改列约束

来源:17golang原创

时间:2026-11-11 07:46:44 341浏览 收藏

在 SQLite 中用 Alembic 修改列约束,核心不是手写一条不存在的 ALTER COLUMN,而是把变更放进 op.batch_alter_table()。对需要重建的操作,Alembic 会按新结构创建临时表、复制旧数据、替换旧表。这样可以把“改 nullable”和“增加 CHECK”写成可审查的迁移脚本。

官方地址:https://alembic.sqlalchemy.org/

下面只讨论表结构迁移,不把业务数据清洗混进迁移脚本。假设 user_account.email 允许为空,现在要改成必填,并给 age 加上非负约束。

先判断:为什么 SQLite 要用 batch 模式

SQLite 的 ALTER TABLE 主要覆盖重命名表、重命名列、添加列和删除列;修改已有列的约束通常不能直接套用其他数据库的写法。Alembic 的 batch 模式把一组单表操作集中起来,在 SQLite 上采用“新表 + INSERT ... SELECT + 替换”的方式完成迁移。

因此,batch_alter_table 不是单纯的语法糖。它明确告诉迁移工具:这些变化要以表结构为整体处理。默认 recreate="auto" 会在 SQLite 遇到需要重建的操作时启用重建;为了让迁移意图更直观,也可以显式写成 recreate="always"。

Alembic batch 模式把迁移操作、SQLite 临时表和约束结构连接起来的静态说明图
图1:batch 迁移的结构说明图,展示迁移脚本、约束定义与 SQLite 重建对象的关系,不是运行截图。

用一个 batch 块批量修改列约束

修改已有列时,建议同时写出 existing_type 和 existing_nullable。它们让 Alembic 在反射旧表和生成新表时有明确的旧结构信息,尤其适合类型、默认值和可空性一起变化的场景。

from alembic import op
import sqlalchemy as sa

def upgrade():
    # SQLite 需要按新表结构重建,统一承载多个约束变化
    with op.batch_alter_table("user_account", recreate="always") as batch_op:
        # 先声明旧类型和旧可空性,避免迁移信息不完整
        batch_op.alter_column(
            "email",
            existing_type=sa.String(length=255),
            existing_nullable=True,
            nullable=False,
        )
        # CHECK 约束要有稳定名称,后续才能精确删除或调整
        batch_op.create_check_constraint(
            "ck_user_account_age_nonnegative",
            sa.text("age >= 0"),
        )

def downgrade():
    # 回滚时恢复可空性,并删除同名 CHECK 约束
    with op.batch_alter_table("user_account", recreate="always") as batch_op:
        batch_op.drop_constraint(
            "ck_user_account_age_nonnegative", type_="check"
        )
        batch_op.alter_column(
            "email",
            existing_type=sa.String(length=255),
            existing_nullable=False,
            nullable=True,
        )

这里的“批量”指同一个表的多项结构变化被一次 batch 操作处理,不是把多张表塞进一个上下文。迁移前必须确认现有行没有 NULL 邮箱,也没有负年龄;否则新表的约束会让数据复制阶段失败。

类型、默认值和约束名称要写完整

如果同时改类型或服务端默认值,继续补齐 existing_server_default、type_ 等旧值信息。不要只写 nullable=False 就假设 Alembic 能推断所有旧元数据;反射结果、方言和命名约定都会影响重建表的最终定义。

SQLite 反射出的部分约束可能没有名字。需要删除或替换 CHECK、外键时,优先在模型和迁移中建立命名约定,或者在 batch 上提供 naming_convention。没有稳定名称时,drop_constraint() 很难准确定位目标。

from alembic import op
import sqlalchemy as sa

def upgrade():
    # 命名约定帮助反射后的无名约束获得可操作的名字
    convention = {
        "ck": "ck_%(table_name)s_%(constraint_name)s",
    }
    with op.batch_alter_table(
        "user_account",
        recreate="always",
        naming_convention=convention,
    ) as batch_op:
        # 类型变化时保留旧类型,避免生成的 CREATE TABLE 缺少上下文
        batch_op.alter_column(
            "age",
            existing_type=sa.Integer(),
            type_=sa.SmallInteger(),
            existing_nullable=True,
        )

如果约束表达式依赖旧列名、索引或外键,先把依赖关系列出来,再决定是否拆成多个迁移。不要把数据回填、删除脏数据和结构替换混在同一个示例里,这些属于不同的发布风险。

SQLite 表重建前后列约束、索引和外键关系的静态说明图
图2:约束生命周期说明图,强调旧表元数据、目标约束和新表结构之间的绑定关系,不是运行截图。

让 autogenerate 生成 batch 结构

项目使用自动生成时,在 Alembic 的 env.py 配置 render_as_batch=True:

context.configure(
    connection=connection,
    target_metadata=target_metadata,
    # 让自动生成的单表变化使用 batch 代码形态
    render_as_batch=True,
)

自动生成的结果仍然需要人工调整,特别是新约束的名称、旧类型和已有数据是否满足目标条件。batch 上下文默认只在 SQLite 需要时执行重建;如果显式使用 recreate="always",其他数据库也会走复制替换路径,不能无条件照搬。

发布前的四项检查

  • 数据兼容:新 NOT NULL 或 CHECK 是否会拒绝已有行。
  • 对象依赖:索引、触发器、视图和外键是否会随表重建正确恢复。
  • 锁与空间:复制整表会产生额外 I/O、临时空间和写锁,生产库要安排窗口。
  • 回滚路径:downgrade() 是否使用同一个约束名,并能恢复旧的 nullable/type。

记住这个判断:只要修改的是 SQLite 不擅长直接 ALTER 的列约束,就优先用命名清晰的 batch_alter_table,把旧结构、目标结构和数据兼容条件写在迁移代码附近。

常见问题

只改 nullable 也要 recreate 吗? 通常要。SQLite 的列约束变化属于需要新表定义的操作,使用 batch 能让 Alembic 统一处理。

为什么一定要给 CHECK 起名字? 因为后续删除或调整约束需要稳定标识;无名约束在反射和回滚时不容易精确匹配。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>