存储过程逐条INSERT百万级数据极慢,因每行触发redo日志刷盘、解析开销及自动事务提交;应改用批量提交(如1000行/批)并显式控制事务。

直接用 WHILE 循环逐条 INSERT 插入百万级数据,在 MySQL 存储过程中默认会极慢——不是语法错,是设计反模式。关键不在“能不能写”,而在“要不要这么写”。
为什么存储过程里的循环插入特别慢
每执行一次 INSERT,InnoDB 默认都会刷一次 redo log(innodb_flush_log_at_trx_commit = 1),加上每次语句解析、权限检查、行锁开销,实际吞吐常低于 300 行/秒。更糟的是,如果没显式事务控制,MySQL 会为每条 INSERT 自动开启又提交一个事务,日志写放大严重。
- 未加
START TRANSACTION/COMMIT:每条语句独立事务 → 日志刷盘次数 = 行数 - 未设
SET NOCOUNT ON(SQL Server)或等效抑制:客户端反复收发影响计数的元数据包 - 字段值依赖函数如
RAND()、NOW()或字符串拼接:每次都要重新计算,无法预编译优化 - 目标表有唯一索引或外键约束:每插一行都触发校验,批量时开销线性增长
必须手动控制事务 + 批量提交
把 100 万行拆成 1000 行/批,每批包在一个事务里,能减少 99.9% 的日志刷盘次数。这是最简单也最有效的提速动作。
- 在
BEGIN后立即写START TRANSACTION - 用
IF i % 1000 = 0 THEN COMMIT; START TRANSACTION;控制提交节奏 - 循环结束后补一次
COMMIT(否则最后一批会丢失) - 避免在循环内调用
SELECT ... FOR UPDATE或复杂子查询,它们会拖慢单次迭代
示例片段:
START TRANSACTION;
WHILE i <= total DO
INSERT INTO usertest (name, mobile) VALUES (CONCAT('u', i), CONCAT('138', LPAD(i, 9, '0')));
IF i % 1000 = 0 THEN
COMMIT;
START TRANSACTION;
END IF;
SET i = i + 1;
END WHILE;
COMMIT;比循环更优的替代方案有哪些
真要插百万级,优先考虑非循环路径。存储过程只是“能做”,不代表“该做”。
-
LOAD DATA INFILE:本地文件导入,速度通常比任何 SQL 逻辑快 5–10 倍,但需文件可被 MySQL 服务端访问 - 递归 CTE(MySQL 8.0+):
WITH RECURSIVE生成数字序列,配合单条INSERT ... SELECT完成,无过程、无循环、纯声明式 - 幂次连接法:用两个小结果集
CROSS JOIN快速撑大行数(如 100 × 1000 = 10 万),适合固定模板数据 - 数字辅助表:提前建好
numbers(n)表,INSERT ... SELECT FROM numbers WHERE n <= 1000000,稳定且易读
这些方法绕过存储过程解释器开销,直接走优化器执行计划,实测插入 100 万行常在 10 秒内完成。
容易被忽略的底层配置项
即使改用批量提交,若 MySQL 配置没调,性能仍卡在瓶颈上。
-
SET SESSION innodb_flush_log_at_trx_commit = 0:关掉每次事务强制刷盘(仅测试环境可用,生产慎用) -
SET SESSION bulk_insert_buffer_size = 64 * 1024 * 1024:增大批量插入缓存,对INSERT ... SELECT类型有效 -
SET UNIQUE_CHECKS = 0和SET FOREIGN_KEY_CHECKS = 0:临时禁用约束校验(插入完记得恢复) - 确保目标表主键是自增且顺序插入:乱序写入会引发页分裂,显著拖慢 InnoDB 插入速度
这些设置必须在存储过程开头显式执行,不能只靠全局配置——因为存储过程运行在 session 级别。


















