MySQL存储过程循环必须先DECLARE变量、游标、处理器,再写逻辑;变量需初始化防NULL失效;WHILE需显式步进防死循环;批量操作须加事务控制大小并避免循环内查总数。

DECLARE变量声明必须在BEGIN之后、逻辑之前
MySQL要求所有DECLARE语句必须紧接在BEGIN后,且必须在任何可执行语句(如SET、INSERT)之前。顺序错乱会直接报错ERROR 1337 (42000): Variable or condition declaration after cursor or handler declaration。
常见错误写法:SET i = 0;出现在DECLARE i INT DEFAULT 0;之前;或在DECLARE中间插入SELECT语句。
- 正确顺序:先
DECLARE所有变量、游标、异常处理器,再写逻辑 - 变量类型要匹配实际用途:计数器用
INT或BIGINT(避免INT溢出导致循环提前终止) - 未初始化的变量值为
NULL,参与计算(如i = i + 1)会导致整个表达式为NULL,循环失效
WHILE循环比REPEAT/LOOP更直观,但需手动控制步进
WHILE是最常用、最易理解的循环结构,适合“先判断后执行”场景,比如按数量批量插入或按ID范围更新。但它不会自动递增变量,必须显式写SET i = i + 1;,漏掉这句就会死循环。
示例中常见的陷阱:
- 条件写成
i <= 100但初始值是1,结果执行100次而非99次——要核对边界是否符合业务预期 - 循环体末尾忘记
SET更新变量,尤其在嵌套IF分支里容易遗漏 - 使用
CONCAT()拼接字符串时传入NULL字段,整条VALUES变成NULL,插入静默失败(无报错但没数据)
批量插入时,每轮INSERT都是一次独立事务,性能差且易锁表
默认情况下,存储过程内每条INSERT都是自动提交的(除非显式开启事务)。这意味着1000次循环 = 1000次磁盘写入 + 1000次行锁获取/释放,不仅慢,还可能阻塞其他读写操作。
真正实用的批量处理必须加事务包裹:
- 开头加
START TRANSACTION;,结尾加COMMIT; - 若中途出错需回滚,加上
DECLARE EXIT HANDLER FOR SQLEXCEPTION捕获异常并ROLLBACK - 注意:事务越大,undo log占用越多,单次循环建议控制在500~5000行,避免撑爆日志空间
不要在循环里反复查COUNT(*)或MAX(id)来控制边界
有些实现会在WHILE条件中写SELECT COUNT(*) INTO @cnt FROM t WHERE ...,每次循环都查一次——这在大数据量下极耗性能,且可能因并发写入导致边界漂移(比如两次查询之间有新数据插入)。
更稳妥的做法是:
- 一次性算好目标总数或最大ID,存入变量,后续只操作该变量
- 用主键范围分片(如
WHERE id BETWEEN @start AND @end),避免LIMIT偏移带来的全表扫描 - 如果依赖动态条件(如“状态为待处理的数据”),应在循环外用临时表固化快照,否则每次查到的数据集可能不一致
NULL——这些细节不验证,过程能编译通过,但跑起来要么不动,要么删错数据。


















