批次上限需依max_allowed_packet、事务日志容量及超时机制三硬约束确定,建议从2000行起步压测,避免触发锁等待超时或服务中断。

事务太大导致锁表或超时,怎么定批次上限
单个事务包几万条数据,基本等于主动触发数据库保护机制:InnoDB 的 undo log 和 redo log 会快速膨胀,主从延迟飙升,甚至直接报 Lock wait timeout exceeded 或 MySQL server has gone away。这不是性能问题,是稳定性红线。
批次上限不能拍脑袋,得看三个硬约束:
-
max_allowed_packet:决定一条INSERT INTO ... VALUES (),(),...最多能塞多少行。默认 4MB,按平均每行 200 字节算,极限约 2 万行——但别真用到极限,留 30% 余量更稳 - 事务日志容量:
innodb_log_file_size× 日志组数,必须大于单批事务产生的 redo 量。否则会频繁刷盘甚至阻塞写入 - 内存与超时:
wait_timeout和应用层 HTTP/连接池超时共同限制事务最长存活时间。插入 10 万行若耗时超 30 秒,大概率被杀
实操建议:从 2000 行起步压测,观察慢日志里 INSERT 平均执行时间 × 批次大小是否
不同场景下该选小批、中批还是大批
“合理”取决于你正在干的事,不是越快越好。
- 高并发 OLTP 写入(如订单创建):选小批(500 行)。锁持有时间短,降低间隙锁冲突概率,避免拖慢其他业务查询
- 后台 ETL 或定时同步:选中批(2000 行)。平衡吞吐与资源占用,多数 MySQL 配置开箱即用
-
离线初始化或灾备恢复:可试大批(10000+ 行),但必须提前调大
innodb_log_file_size和innodb_buffer_pool_size,并确认innodb_flush_log_at_trx_commit = 2
注意:大批 ≠ 无脑大。PostgreSQL 对单事务 WAL 大小更敏感,SQL Server 的 SqlBulkCopy.BatchSize 超过 10000 容易触发内存 OOM,这些都不是 MySQL 的经验能直接套用的。
代码里控制事务边界,别依赖框架自动 batch
像 JdbcTemplate.batchUpdate() 或 executemany() 这类封装,只解决语句拼接,不控制事务提交时机。它们默认仍走 autocommit,或者把全部数据塞进一个事务——后者在数据源很大时就是隐患。
- 显式用
BEGIN TRANSACTION/START TRANSACTION开启事务,COMMIT结束,中间只放当前批次的INSERT语句 - 流式读取时(如 Python 的
cursor.fetchmany(2000)),每 fetch 一批就处理一批,不把全量数据 load 进内存再切片 - 异常捕获后,记录当前批次起始 offset,支持断点续插;不要 catch 了就
ROLLBACK全量重试
Java 中如果用 DataSourceTransactionManager,务必配 PROPAGATION_REQUIRES_NEW 确保每个批次事务隔离;Python 用 pymysql 时记得关掉 autocommit=True 默认值。
容易被忽略的数据库侧配合动作
光靠应用层分批,效果打七折。真正卡脖子的常在数据库配置和运行时状态。
- 导入前临时关约束:
SET FOREIGN_KEY_CHECKS = 0、SET UNIQUE_CHECKS = 0,完事再开——但前提是数据本身已校验干净 - 索引策略:非必要二级索引,导入前
DROP INDEX,完成后CREATE INDEX。主键聚集索引顺序插入能避免页分裂,乱序插入时性能可能差 3 倍 - 日志刷盘让步:
innodb_flush_log_at_trx_commit = 2可将 I/O 降 5 倍,但机器宕机有最多 1 秒数据丢失风险,仅限离线场景
最常被跳过的一步:没在预发环境用真实数据量压测。线上 INSERT 耗时翻倍,往往是因为预发没开 slow_query_log,漏掉了隐式锁等待或索引失效。

















