1000–5000行/批是常见推荐值,因其兼顾redo log容量限制(避免超innodb_log_file_size的30%触发强制刷盘)与锁持有时间(通常1–5秒内提交,避开默认50秒锁等待超时),并需结合autocommit=0、连接状态清理及避免executemany伪原子性等实操要点。

为什么1000–5000行/批是常见推荐值
这不是拍脑袋定的数字,而是由InnoDB日志机制和锁持有时间共同决定的。每批数据写入会生成redo log,若单批过大(比如超1万行),可能超过innodb_log_file_size的30%,触发强制刷盘阻塞;同时,行锁在事务提交前不会释放,1000–5000行通常能在1–5秒内完成提交,刚好避开innodb_lock_wait_timeout(默认50秒)的敏感区间。
实际取值还要看单行平均字节数:文本字段多、JSON长的表建议往1000靠;纯整型主键+短字符串可试探到5000。别硬套数字,用SELECT @@innodb_log_file_size;查当前值再算。
autocommit=0必须显式设置,否则分批失效
很多ORM或连接池默认开启autocommit=1,此时即使代码里写了BEGIN,只要下一条语句是SELECT或SET,事务就悄悄提交了——你自以为在分批,其实每条INSERT都在独立小事务里跑,反而放大锁竞争和日志压力。
- 执行前先确认:
SELECT @@autocommit;,不是1就安全 - 不放心就加一句:
SET autocommit = 0; - 用连接池时,每次分批前加
ROLLBACK;清空残留状态,避免复用脏连接
executemany()不等于事务原子性,慎用
executemany()只是客户端批量发送,MySQL服务端仍按单条语句解析执行。某一行违反唯一键约束时,前面成功的行已落盘,conn.rollback()救不回来——这和你想的“整批回滚”完全相反。
更可控的做法是拼单条多值INSERT INTO t (a,b) VALUES (),(),()...,再用execute()发过去。虽然拼SQL麻烦点,但语义清晰:整条语句要么全成,要么全败。
如果必须用executemany(),得提前在应用层去重:array_unique($rows, SORT_REGULAR)(PHP)或用set(tuple(row) for row in data_rows)(Python),否则同一批里两条相同主键的数据会直接报错,且错误位置难定位。
LOAD DATA INFILE不能自动分片,得先切文件
LOAD DATA INFILE本身是原子操作,不支持事务内部分批。想用它做大批量导入又不想锁死,只能先把大文件按行数切开,比如用split -l 2000 bigfile.csv chunk_生成多个小文件,再循环执行:
LOAD DATA INFILE '/path/chunk_aa' INTO TABLE t_user FIELDS TERMINATED BY ',';
注意两点:
- 每个
LOAD DATA INFILE语句自己就是一个事务,所以切分后仍要控制单个文件大小 - 临时禁用索引和外键(
ALTER TABLE t_user DISABLE KEYS;)只对MyISAM有效,InnoDB不认这个,得用SET FOREIGN_KEY_CHECKS = 0;和ALTER TABLE ... DROP INDEX手动处理
executemany()带来的“伪原子性”幻觉——这些地方出问题,往往比性能慢更难排查。


















