存储过程循环插入慢因每行触发完整事务流程且无法并行,批量插入(如insert...select+递归cte或load data infile)可提升数倍至数量级性能。

为什么存储过程循环插入在 MySQL 里反而很慢
因为每次 INSERT 都是独立语句,MySQL 默认每条都走完整事务流程(即使没显式开事务),加上存储过程本身解释执行、缺乏 JIT 优化,10 万行可能要几分钟。更糟的是,如果表有索引、外键或触发器,耗时会指数级上升。
- 默认 autocommit=1,相当于每插一行就 commit 一次
- 存储过程里的
LOOP或WHILE是逐行控制流,无法并行 -
INSERT ... VALUES (...), (...), ...批量写入比单行快 5–20 倍,但存储过程里拼字符串容易溢出或 SQL 注入(哪怕测试数据也要防)
用 INSERT INTO ... SELECT + 递归 CTE(MySQL 8.0+)替代循环
这是目前最干净、可控、不依赖外部工具的纯 SQL 方案。CTE 生成数字序列,再映射成你需要的测试字段,一次批量插入完成。
INSERT INTO users (id, name, created_at) WITH RECURSIVE seq AS ( SELECT 1 AS n UNION ALL SELECT n + 1 FROM seq WHERE n
- 必须确保
cte_max_recursion_depth足够大(SET SESSION cte_max_recursion_depth = 1000000) - 避免在 CTE 中调用
NOW()多次——它会被求值多次,但时间戳几乎一样;如需精确模拟时间分布,改用DATE_ADD(NOW(), INTERVAL n SECOND) - 如果 MySQL 版本 ERROR 1064 (42000): You have an error in your SQL syntax
低版本 MySQL(5.7 及以下)用临时表 + 自增 ID 模拟批量
没有 CTE 就造一个数字表。核心思路:用系统表(如 information_schema.columns)的行数做“源”,通过 CROSS JOIN 快速膨胀,再用变量生成序号。
CREATE TEMPORARY TABLE nums AS
SELECT @row := @row + 1 AS n
FROM information_schema.columns a,
information_schema.columns b,
(SELECT @row := 0) r
LIMIT 100000;
INSERT INTO users (id, name, created_at)
SELECT n, CONCAT('user_', n), NOW() FROM nums;
-
LIMIT必须写,否则可能生成上百万行(取决于系统表大小) - 临时表只在当前会话有效,不用
DROP,但要注意会话断开就消失 - 如果提示
Unknown system variable 'row',说明变量未初始化——@row := 0那一行不能少
真正快的方案其实不靠存储过程
如果你只是想灌测试数据,存储过程从来就不是最优解。真正省时省力的方式是绕过 SQL 解析层:
- 用
mysqldump导出空表结构,再用脚本(Python/awk)生成纯INSERT文本文件,最后用mysql 导入——关闭唯一性检查和 autocommit 后,百万行几秒搞定 - 用
LOAD DATA INFILE:生成 CSV,设置DISABLE KEYS和SET FOREIGN_KEY_CHECKS=0,速度比任何 SQL 插入都高一个数量级 - 存储过程适合封装业务逻辑,不适合当“数据生成器”——它的设计目标根本不是吞吐量
真正卡住的人,往往是在存储过程里死磕循环次数,却忘了 MySQL 的瓶颈从来不在“怎么写循环”,而在“怎么让磁盘和日志少跑几趟”。











