bulk_insert_mappings 比 add() 快得多,因其跳过 ORM 实例化、属性校验与事件钩子,直接将字典列表转为批量 INSERT 语句;10 万条数据下耗时约 10 秒,远低于 add() 循环的 300 秒,且需注意手动补全默认值、严格字段对齐、事务批量提交及关闭日志。

bulk_insert_mappings 为什么比 add() 快得多
因为 session.add() 每调用一次,就创建一个 ORM 实例、触发属性校验、生成一条 INSERT 语句并绑定参数;而 bulk_insert_mappings() 直接接收字典列表,跳过实例化和事件钩子,合并成少数几条批量 INSERT 语句发送。10 万条数据下,前者可能耗时 300 秒,后者约 10 秒。
常见错误现象:用循环写 session.add(User(...)) + session.commit(),结果卡死或内存爆掉。
- 必须确保字典 key 与模型字段名完全一致(大小写敏感)
- 不支持自动生成的默认值(如
default=func.now()),需手动填入 - 不会调用
@validates或before_insert钩子,业务逻辑要前置处理
事务控制:别在循环里 commit
每次 commit() 都会强制刷盘、释放锁、重置连接状态。高频提交是性能杀手。
正确做法是把所有批量操作包在一个事务里,用 with session.begin(): 或显式 session.begin() + session.commit()。
立即学习“Python免费学习笔记(深入)”;
- 单事务插入 10 万条没问题,但超过 50 万建议分批(如每 1 万条 commit 一次),避免 WAL 日志膨胀或锁等待
- PostgreSQL 下大事务可能触发
transaction timeout,MySQL 可能报max_allowed_packet错误 - 记得设
autoflush=False,否则中间调用session.query()可能意外触发 flush
Core 层 insert() + execute 更快,但要注意字段映射
如果连 ORM 映射层都想绕过,直接用 SQLAlchemy Core 的 insert() 构造 + execute(),性能通常略优于 bulk_insert_mappings,尤其在字段多、数据量极大时。
典型错误:传错表对象 —— 要用 User.__table__ 或 users_table,不能传 User 类本身(除非配合 ORM session.execute)。
- 示例:
session.execute(insert(User), data)是合法 ORM 用法;conn.execute(User.__table__.insert(), data)是 Core 用法 - Core 方式支持原生数据库语法扩展,比如 PostgreSQL 的
ON CONFLICT,但得手写冲突处理逻辑 - 字段名必须和表定义严格对齐,不校验类型,出错时错误信息更晦涩(比如
KeyError或数据库报列不存在)
连接池与日志:容易被忽略的拖慢点
即使用了批量插入,如果连接池太小或日志全开,照样卡在 I/O 上。
生产环境务必关掉 echo=True,否则每条 SQL 都写日志,I/O 占比能到 35%;同时检查连接池配置是否匹配写入压力。
-
pool_size=10+max_overflow=20适合中等并发批量写入;纯导入任务可临时设pool_size=1避免连接争抢 -
echo=False是底线,更激进可设logging.getLogger('sqlalchemy.engine').setLevel(logging.WARNING) - 批量导入期间避免混用读操作,防止连接被长查询占用,导致写入线程阻塞


















