mysql 8.0中不推荐用while循环单行插入,因其默认autocommit=1导致每行触发完整事务开销;高效方案是用存储过程封装递归cte配合批量insert…select,需设cte_max_recursion_depth并避免cte内多次调用now()/rand()。

直接上结论:MySQL 8.0 中不推荐用传统单行循环式存储过程批量造数据,它慢、卡、易超时;真正高效的做法是用存储过程封装递归 CTE + 批量 INSERT … SELECT,或退而求其次——用带分批提交的拼接式 INSERT(如你知识库中那个 batch_insert 过程)。
为什么 MySQL 8.0 下普通 WHILE 循环插入极慢?
不是语法错,是执行模型硬伤:
- 默认
autocommit=1,每条INSERT都触发完整事务流程(binlog 写入 + redo log 刷盘 + 索引更新) -
WHILE是解释执行,无向量化优化,10 万行常卡在几分钟甚至报ERROR 1205 (40001): Deadlock found或超时 - 若表有二级索引、外键或触发器,性能断崖式下跌——每行都要校验约束
-
RAND()在循环内多次调用时,可能被优化器复用(尤其在INSERT ... SELECT场景),导致字段值重复
推荐方案:用存储过程调用递归 CTE 批量插入
这是 MySQL 8.0+ 唯一既纯 SQL、又接近 LOAD DATA INFILE 性能的方案。关键不在“能不能写”,而在“怎么写不翻车”:
- 必须提前设会话变量:
SET SESSION cte_max_recursion_depth = 1000000;(插 100 万行至少要这个值) -
WITH RECURSIVE必须紧跟INSERT,不能拆成两步(否则 CTE 结果集丢失) - 避免在 CTE 的
SELECT里多次调用NOW()或RAND()—— 它们会被反复求值,但时间戳几乎一样,随机性也难控 - 用
id衍生其他字段更稳定,比如:CONCAT('user_', n)比CONCAT('user_', FLOOR(RAND()*10000))更不易重复
示例(插入 50 万行):
DELIMITER //
CREATE PROCEDURE insert_bulk_cte(IN cnt BIGINT)
BEGIN
SET SESSION cte_max_recursion_depth = cnt + 10;
SET autocommit = 0;
INSERT INTO test(id, name)
WITH RECURSIVE seq AS (
SELECT 1 AS n
UNION ALL
SELECT n + 1 FROM seq WHERE n <hr><h3><h3>备选方案:分批拼接 INSERT(兼容 5.7/8.0,更可控)</h3></h3><p>你知识库里的 <code>batch_insert</code> 过程就是典型代表。它不依赖 CTE,靠字符串拼接 + <code>PREPARE</code> 实现批量写入,优势在于:</p>
- 内存占用低:逐批生成、执行、释放,不构建百万行临时结果集
- 参数自动容错:
p_batch_size ≤ 0时设为 1000,p_total_count ≤ 0直接报错 - 可精确控制每批大小(如 5000 行/批),规避
max_allowed_packet限制 - 注意陷阱:
@sql拼接时若含单引号(如'test'),必须用两个单引号转义,否则语法错误
最容易被忽略的三个点
很多人跑通了就以为万事大吉,但线上压测一跑就崩:
- 没关
autocommit就跑循环 → 插 1 万行可能耗时 3 分钟以上 - 用
RAND()生成主键或唯一字段 → 极大概率撞Duplicate entry导致整个事务回滚 - 在存储过程中调用
NOW()或UUID()作为字段值 → 时间戳全一样 / UUID 生成逻辑受 session 变量影响,不可复现
真正稳定的测试数据,核心是「可预测的随机」:用自增序号派生字段,或固定 seed 的 RAND(12345),而不是放任 MySQL 自己“发挥”。











