mysql游标嵌套必须用begin...end块隔离每层逻辑,变量需独立命名并显式重置,内层sql须动态引用外层fetch后的变量,避免静态查询和作用域混乱。

MySQL游标嵌套必须用BEGIN...END块隔离内层逻辑
不加BEGIN...END包裹内层游标声明,会导致语法错误或变量作用域混乱。MySQL解析器会把内层DECLARE语句当作外层作用域的一部分,而外层已声明同名变量(比如多个done)就会报ERROR 1337 (42000): Variable 'done' is not declared。
正确做法是:每层游标逻辑都用独立的BEGIN...END块封装,包括变量声明、游标定义、CONTINUE HANDLER、OPEN/FETCH/CLOSE全过程。
- 外层游标变量(如
outer_done)和内层(如inner_done)必须命名不同,不能复用 -
CONTINUE HANDLER FOR NOT FOUND只对紧邻的游标生效,不能跨块共享 - 嵌套过深(如四层以上)时,建议拆成多个存储过程调用,避免栈溢出或调试困难
内层游标SQL必须依赖外层变量且不能提前固化
常见错误是把内层游标写成静态查询,比如DECLARE cur2 CURSOR FOR SELECT * FROM t2 WHERE id = 123——这会让内层始终查固定值,失去“逐条关联”的意义。真正需要的是动态条件,且该条件来自外层FETCH后的变量。
但要注意:这个变量必须在外层FETCH之后、内层OPEN之前赋值完成;否则内层游标打开时读到的是初始默认值(如0或NULL),导致查不到数据。
- 外层
FETCH后立即检查IF NOT outer_done THEN ...,再进内层块 - 内层游标SQL中直接引用外层变量(如
WHERE parent_id = outer_id),不要用SET @var := ...间接传参 - 若外层变量为
NULL,内层查询结果为空,FETCH第一次就触发NOT FOUND,需确保CONTINUE HANDLER已就位
嵌套循环中done标志重置极易被忽略
很多人以为内层done变量在每次进入BEGIN...END块时自动初始化,其实不会。MySQL中块内DECLARE done INT DEFAULT FALSE只在**块首次执行时生效**;如果外层循环多次进入同一内层块,第二次开始done仍保持上次结束时的值(通常是TRUE),导致内层循环直接跳过。
必须显式重置:在每次OPEN cur_inner前,加一句SET inner_done = FALSE(或0)。
- 漏掉这步,内层循环只执行第一次,后续全部跳过
- 不能靠
DECLARE inner_done INT DEFAULT FALSE代替,它不重复执行 - 同理,所有内层游标相关的状态变量(如计数器、临时结果变量)都应在每次循环开始前重置
性能差不是因为嵌套本身,而是缺少索引和批量替代方案
游标嵌套本质是行级处理,哪怕只有三层,也可能产生O(n×m×k)级扫描。但实际慢的主因常被误判——比如WHERE条件字段没索引,或内层游标反复全表扫描。
上线前务必确认:EXPLAIN内层游标SQL是否走了索引;外键字段是否加了索引;是否存在SELECT *拖慢网络传输。
- 能用
INSERT ... SELECT或JOIN一次性处理的,坚决不用游标嵌套 - 三层以上嵌套几乎必然卡顿,应改用临时表+分步更新
- 事务中嵌套游标必须控制粒度,避免长事务锁表;
COMMIT不能放在最外层末尾,要按业务单元分段提交
游标嵌套真正的难点不在语法,而在状态变量生命周期、条件变量可见性、以及每一层打开/关闭时机的精确控制——这些地方错一点,整个逻辑就静默失效,还很难通过日志定位。











