Alembic生成复合主键致MySQL表定义错误的问题排查
解决MySQL中SQLAlchemy复合主键含自增字段的迁移报错问题
问题根源
MySQL有严格规则:自增字段必须是主键的一部分,且如果是复合主键,自增字段必须是主键的第一个元素。你遇到的1075: Incorrect table definition; there can be only one auto column and it must be defined as a key报错,就是因为Alembic自动生成的迁移脚本把record_valid_from放在了复合主键的首位,自增的id排在后面,违反了这个规则。
解决方案
1. 修正模型定义,显式指定复合主键顺序
在模型中通过__table_args__明确声明复合主键的顺序,确保自增的id作为第一个主键字段:
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from sqlalchemy import DateTime, Boolean, PrimaryKeyConstraint from datetime import datetime def get_current_timestamp(): return datetime.now() class classA(DeclarativeBase): __tablename__ = 'classA' id: Mapped[int] = mapped_column(primary_key=True, index=True, autoincrement=True) record_valid_from: Mapped[datetime] = mapped_column(DateTime, primary_key=True, default=get_current_timestamp ) active: Mapped[bool] = mapped_column(Boolean, default=True, comment="TRUE if latest version, FALSE otherwise" ) # 显式指定复合主键顺序,id在前 __table_args__ = ( PrimaryKeyConstraint('id', 'record_valid_from'), )
2. 手动修正Alembic迁移脚本
自动生成迁移脚本后,调整upgrade函数中的表创建逻辑,确保:
- 复合主键约束的顺序是
('id', 'record_valid_from') - 自增字段
id的定义正确关联到主键
修正后的迁移代码:
def upgrade() -> None: op.create_table('classA', sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), sa.Column('name', sa.String(length=100), nullable=False), sa.Column('record_valid_from', sa.DateTime(), nullable=False), sa.Column('active', sa.Boolean(), nullable=False), sa.PrimaryKeyConstraint('id', 'record_valid_from') ) op.create_index(op.f('ix_classA_id'), 'classA', ['id'], unique=False)
为什么之前的尝试失败
- 第一种方案(表创建后加主键):创建表时Alembic已经生成了默认的复合主键约束,再次调用
op.create_primary_key会导致重复定义主键,触发1068: Multiple primary key defined错误。 - 第二种方案(移除原主键后加新主键):移除原主键后,自增的
id不再是主键的一部分,MySQL会取消其自增自动赋值的特性,因此插入新数据时会要求id字段提供默认值,触发1364: Field 'id' doesn't have a default value错误。
内容的提问来源于stack exchange,提问作者T4cceptor
相关产品推荐
相关产品推荐

