You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Alembic迁移更新列时服务器默认值被识别为null的问题

问题分析与解决方案

核心问题1:list.append()返回值导致更新为Null

Python的list.append()是原地修改列表,返回值为None。你在values()里直接用subscription.has_bubble_in_countries.append(Country.NL),相当于把None赋值给了has_bubble_in_countries,直接触发非空约束错误。

核心问题2:SQLAlchemy未自动填充服务器默认值

server_default='{}'是数据库层面的默认值,仅在插入新行且未指定该列值时生效。查询现有数据时,SQLAlchemy不会自动将NULL替换为服务器默认值——如果数据库中存在实际为NULL的行(即使pgAdmin显示为{},可能存在类型映射差异),读取后会得到None。


修复步骤

1. 修正列表更新逻辑

不要直接使用append()的返回值,先复制原有列表(或初始化空列表),添加元素后再赋值:

def upgrade():
    connection = op.get_bind()

    for subscription in connection.execute(subscription_old_table.select()):
        if subscription.has_bubble_v1:
            # 处理可能的None值,初始化为空列表
            current_countries = subscription.has_bubble_in_countries or []
            # 复制列表并添加新元素
            new_countries = list(current_countries)
            new_countries.append(Country.NL)
            
            connection.execute(
                subscription_old_table.update().where(
                    subscription_old_table.c.id == subscription.id
                ).values(
                    has_bubble_in_countries=new_countries,
                )
            )
    op.drop_column('Subscription', 'has_bubble_v1')

2. 提前修复数据库中的Null值(可选)

如果确实存在NULL行,可以先执行一次批量更新,将所有NULL替换为默认空数组:

def upgrade():
    connection = op.get_bind()
    
    # 先修复所有NULL值为默认空数组
    connection.execute(
        subscription_old_table.update()
        .where(subscription_old_table.c.has_bubble_in_countries.is_(None))
        .values(has_bubble_in_countries=[])
    )
    
    # 再执行原有迁移逻辑
    for subscription in connection.execute(subscription_old_table.select()):
        if subscription.has_bubble_v1:
            current_countries = list(subscription.has_bubble_in_countries)
            current_countries.append(Country.NL)
            connection.execute(
                subscription_old_table.update().where(
                    subscription_old_table.c.id == subscription.id
                ).values(has_bubble_in_countries=current_countries)
            )
    op.drop_column('Subscription', 'has_bubble_v1')

3. 优化:使用批量SQL更新(提升效率)

如果数据量较大,循环逐行更新效率较低,可以用PostgreSQL的ARRAY_APPEND函数直接在数据库层面操作,避免Python层面的列表处理:

from sqlalchemy import func

def upgrade():
    connection = op.get_bind()
    
    # 先修复NULL值
    connection.execute(
        subscription_old_table.update()
        .where(subscription_old_table.c.has_bubble_in_countries.is_(None))
        .values(has_bubble_in_countries=[])
    )
    
    # 批量更新满足条件的行
    connection.execute(
        subscription_old_table.update()
        .where(subscription_old_table.c.has_bubble_v1.is_(True))
        .values(
            has_bubble_in_countries=func.array_append(
                subscription_old_table.c.has_bubble_in_countries,
                Country.NL.value  # 枚举类型需传入对应字符串值
            )
        )
    )
    
    op.drop_column('Subscription', 'has_bubble_v1')

关于Alembic识别服务器默认值的说明

Alembic/SQLAlchemy不会自动将查询到的NULL替换为server_default的值,因为server_default是数据库的插入时默认行为,不是查询时的自动转换。如果需要确保查询时获取到默认值,要么在查询时手动处理(如or []),要么提前将数据库中的NULL批量更新为默认值。

内容的提问来源于stack exchange,提问作者Razvan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 17:50:36