一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

如何在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 循环插入极慢?

不是语法错,是执行模型硬伤:

  1. 默认 autocommit=1,每条 INSERT 都触发完整事务流程(binlog 写入 + redo log 刷盘 + 索引更新)
  2. WHILE 是解释执行,无向量化优化,10 万行常卡在几分钟甚至报 ERROR 1205 (40001): Deadlock found 或超时
  3. 若表有二级索引、外键或触发器,性能断崖式下跌——每行都要校验约束
  4. RAND() 在循环内多次调用时,可能被优化器复用(尤其在 INSERT ... SELECT 场景),导致字段值重复

推荐方案:用存储过程调用递归 CTE 批量插入

这是 MySQL 8.0+ 唯一既纯 SQL、又接近 LOAD DATA INFILE 性能的方案。关键不在“能不能写”,而在“怎么写不翻车”:

  1. 必须提前设会话变量:SET SESSION cte_max_recursion_depth = 1000000;(插 100 万行至少要这个值)
  2. WITH RECURSIVE 必须紧跟 INSERT,不能拆成两步(否则 CTE 结果集丢失)
  3. 避免在 CTE 的 SELECT 里多次调用 NOW()RAND() —— 它们会被反复求值,但时间戳几乎一样,随机性也难控
  4. 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 实现批量写入,优势在于:

  1. 内存占用低:逐批生成、执行、释放,不构建百万行临时结果集
  2. 参数自动容错:p_batch_size ≤ 0 时设为 1000,p_total_count ≤ 0 直接报错
  3. 可精确控制每批大小(如 5000 行/批),规避 max_allowed_packet 限制
  4. 注意陷阱:@sql 拼接时若含单引号(如 'test'),必须用两个单引号转义,否则语法错误

最容易被忽略的三个点

很多人跑通了就以为万事大吉,但线上压测一跑就崩:

  1. 没关 autocommit 就跑循环 → 插 1 万行可能耗时 3 分钟以上
  2. RAND() 生成主键或唯一字段 → 极大概率撞 Duplicate entry 导致整个事务回滚
  3. 在存储过程中调用 NOW()UUID() 作为字段值 → 时间戳全一样 / UUID 生成逻辑受 session 变量影响,不可复现

真正稳定的测试数据,核心是「可预测的随机」:用自增序号派生字段,或固定 seed 的 RAND(12345),而不是放任 MySQL 自己“发挥”。

热门栏目