4. MetaData与数据库反射
在 SQLAlchemy 中,MetaData 是描述数据库结构的“总目录”,它把一组 Table、Column、约束、索引等对象组织在一起,并作为生成 SQL DDL、执行反射、绑定 Engine 的入口。可以把它理解为 Python 侧的数据库 schema 容器:一个 MetaData 实例对应一个逻辑数据库命名空间,里面可以挂载多张表,每张表又由若干列和约束构成。
Table 定义是声明式建模的基础。使用 Table 时,需要提供表名、MetaData 实例以及若干 Column 对象。Column 的第一个参数是列名,第二个参数是类型,例如 Integer、String(50)、Numeric(10,2)、DateTime、Boolean。类型不仅影响 DDL 生成,也影响 Python 侧的值处理与结果集转换。常见约束包括主键 primary_key=True、非空 nullable=False、唯一 unique=True、默认值 default 与 server_default、外键 ForeignKey、检查约束 CheckConstraint。其中 default 由 SQLAlchemy 在 INSERT 时生成,server_default 则写入 DDL 交给数据库执行,两者语义不同,工程中要按“应用侧生成”还是“数据库侧生成”来选择。
下面是一个显式定义两张表的示例,包含主键、外键、唯一约束和服务器默认值:
from sqlalchemy import (
MetaData, Table, Column, Integer, String,
Numeric, DateTime, Boolean, ForeignKey, UniqueConstraint, func
)
metadata = MetaData()
user = Table(
"user", metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("username", String(50), nullable=False, unique=True),
Column("email", String(120), nullable=False),
Column("is_active", Boolean, nullable=False, server_default="1"),
Column("created_at", DateTime, nullable=False, server_default=func.now()),
UniqueConstraint("email", name="uq_user_email"),
)
order = Table(
"order", metadata,
Column("id", Integer, primary_key=True),
Column("user_id", Integer, ForeignKey("user.id"), nullable=False),
Column("amount", Numeric(10, 2), nullable=False),
Column("status", String(20), nullable=False, server_default="pending"),
)
定义完成后,可以通过 metadata.create_all(engine) 在目标库中建表;如果只想查看将执行的 DDL,可用 CreateTable(order).compile(dialect=engine.dialect) 打印语句。这里要注意:create_all 只创建不存在的表,不会修改已有表结构,因此生产环境通常配合迁移工具使用。
当数据库已经存在、而 Python 侧还没有模型时,就需要反射。反射的核心入口是 MetaData.reflect(bind=engine) 或 Table(..., autoload_with=engine)。前者一次性加载库中所有表,后者按需加载单表。反射会读取表名、列名、类型、主键、外键、唯一约束、索引等信息,并还原为 SQLAlchemy 对象。对于大型库,建议只反射需要的表,避免一次性加载过多元数据。
from sqlalchemy import create_engine, MetaData, Table
engine = create_engine("mysql+pymysql://user:pwd@localhost:3306/shop")
# 方式一:反射全部表
metadata = MetaData()
metadata.reflect(bind=engine)
print(metadata.tables.keys())
# 方式二:只反射单表
user_ref = Table("user", MetaData(), autoload_with=engine)
for col in user_ref.columns:
print(col.name, col.type, col.nullable, col.primary_key)
反射结果可以直接用于查询,也可以作为自动生成模型的输入。SQLAlchemy 本身不内置“反射转 ORM 模型”的命令,但生态中有 sqlacodegen 等工具,能够连接数据库并输出 declarative 模型代码。典型用法是:
sqlacodegen mysql+pymysql://user:pwd@localhost:3306/shop --outfile models.py
生成后的模型会包含 Base、__tablename__、Column、ForeignKey、relationship 等结构。需要人工复核的点包括:类型映射是否符合预期、枚举是否被识别、复合主键与外键关系是否正确、默认值是否保留、命名是否与项目规范一致。自动生成是起点,不是终点。
反射与自动生成虽然高效,但有几个常见坑需要提前规避。第一,类型映射并非一一对应,例如 MySQL 的 TINYINT(1) 可能被识别为 Boolean,也可能被识别为 Integer,取决于方言版本;ENUM 在部分方言中会退化为 String。第二,反射默认不加载视图、存储过程、触发器等对象,若业务依赖视图,需要单独处理。第三,外键关系在反射后不会自动生成 ORM 的 relationship,需要手动补充或依赖生成工具推断。第四,server_default 在反射时可能表现为字符串或 text() 对象,直接用于 ORM 时要注意类型转换。
从工程实践看,MetaData 与反射通常配合使用:开发阶段用显式 Table 定义或 declarative 模型描述新结构;接手遗留库时先用 MetaData.reflect 摸清现状,再用 sqlacodegen 生成初版模型,最后人工整理命名、类型、关系和默认值。这样既能快速获得可运行的 ORM 层,又能保证模型与真实数据库结构一致,为后续查询、迁移和性能优化打下可靠基础。