最新下载
热门教程
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
如何在MySQL 8.0中利用存储过程批量生成测试数据?
时间:2026-08-23 20:34:48 编辑:袖梨 来源:一聚教程网
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)BEGINSET SESSION cte_max_recursion_depth = cnt + 10;SET autocommit = 0;INSERT INTO test(id, name) WITH RECURSIVE seq AS (SELECT 1 AS nUNION ALLSELECT n + 1 FROM seq WHERE n备选方案:分批拼接 INSERT(兼容 5.7/8.0,更可控)
你知识库里的
batch_insert过程就是典型代表。它不依赖 CTE,靠字符串拼接 +PREPARE实现批量写入,优势在于:
- 内存占用低:逐批生成、执行、释放,不构建百万行临时结果集
- 参数自动容错:
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 自己“发挥”。