单条插入千万数据必然断连,因超时限制;应改用批量插入+显式事务,每批1万~10万行,并禁用autocommit;mysql 8.0+推荐递归cte替代存储过程。

存储过程单条插入千万数据会直接断连
MySQL 默认的 wait_timeout 和客户端超时(如 MySQL Workbench 的 read timeout)通常设为 30 秒,而纯循环单条 INSERT 插入 1000 万行,即使每行只要 3ms,也需约 8 小时 —— 实际中更慢,且早就在 30 秒内触发 ErrorCode: 2013. Lost connection to MySQL server during query。这不是代码写错了,是设计上就不可行。
解决思路不是调大 timeout 到几小时,而是避免单条执行:
- 必须显式开启事务(
START TRANSACTION),把多条插入包进一个事务里 - 每批插入控制在 1 万~10 万行之间(例如每次循环插入 5 万条再
COMMIT),防止事务日志暴涨或内存溢出 - 禁用自动提交:
SET autocommit = 0,否则每条INSERT都隐式提交,性能归零
MySQL 8.0 推荐用 CTE + INSERT … SELECT 替代存储过程
MySQL 8.0+ 原生支持递归 CTE,比存储过程快一个数量级,且无过程体、无变量、无循环开销。生成 100 万行实测耗时通常
关键点:
-
WITH RECURSIVE的终止条件必须明确,比如WHERE n ,否则无限递归导致 OOM - 不能在 CTE 中直接引用目标表(会报
Error 1137: Can't reopen table),必须用子查询或中间表绕过 - 记得提前关闭自动提交:
SET autocommit = 0,最后COMMIT
示例:
WITH RECURSIVE gen AS ( SELECT 1 AS n UNION ALL SELECT n + 1 FROM gen WHERE n <h3>用 MEMORY 表做中转能提速 3–5 倍</h3><p>直接往 InnoDB 表插千万行,瓶颈常在磁盘刷写和索引维护。先插到 <code>ENGINE=MEMORY</code> 表,再批量 <code>INSERT ... SELECT</code> 导入目标表,可显著降低锁竞争和 I/O 压力。</p><p>但要注意:</p>
-
MEMORY表数据全驻内存,总大小受max_heap_table_size限制(默认 16MB),需提前调大,例如:SET max_heap_table_size = 1024 * 1024 * 1024;(1GB) -
MEMORY不支持TEXT/BLOB,字段类型要精简,比如用VARCHAR(32)存 UUID 而非CHAR(36) - 导入完成后务必
DROP TABLE IF EXISTS memory_tmp;,否则内存不释放
UUID() 和 RAND() 在大数据量下有隐藏性能坑
UUID() 每次调用都涉及时间戳+MAC+随机数拼接,比 FLOOR(RAND()*N) 慢 2–3 倍;而 RAND() 在复杂表达式中可能被多次求值(尤其嵌套函数里),进一步拖慢。
优化建议:
- 避免在
INSERT语句里反复调用UUID():改用自增 ID + 固定前缀,如CONCAT('uid_', id) - 如果真要随机字符串,先建好函数
rand_string(n)并加DETERMINISTIC,让 MySQL 缓存执行计划 - 对时间字段,别用
NOW()——它每次计算,改用DATE_SUB(NOW(), INTERVAL FLOOR(RAND()*3650) DAY)即可,但注意不要在循环中重复算 NOW()
真正卡住你的往往不是“怎么写”,而是没关 autocommit、没分批次、没换引擎、还在用单条插入硬扛 —— 这些细节漏掉一个,1000 万就变成一整晚的等待。











