not found 是游标遍历结束的正常信号,非异常;因 continue handler for not found 全局生效,循环内任意 select ... into 无结果均会触发并置 done=1,导致游标提前退出;须为游标单独声明 done 变量、fetch 后立即检查、并在无关查询后手动 set done = 0 隔离干扰。

NOT FOUND 不是异常,而是游标遍历结束的正常信号。MySQL 存储过程中用 CONTINUE HANDLER FOR NOT FOUND 捕获它,本质是流程控制,不是错误处理。
为什么 NOT FOUND 会提前触发循环退出?
根本原因是:任何 SELECT ... INTO 语句在查不到数据时,都会触发同一个 NOT FOUND 条件,从而把你的游标结束标志(比如 done)设为 1 —— 即使那条 SELECT 和游标完全无关。
- 游标声明后定义的
CONTINUE HANDLER FOR NOT FOUND是全局生效的,作用域覆盖整个BEGIN...END块 - 你在循环体内执行
SELECT x INTO y FROM t WHERE id = ?,若没匹配到行,done就被置为 1 - 下一轮
FETCH还没执行,IF done = 1 THEN LEAVE就直接跳出循环了
如何隔离游标结束和普通查询失败?
必须为游标专用的结束判断建立独立变量,并在每次 FETCH 后立即检查、重置干扰项。关键动作有三步:
- 声明两个变量:
DECLARE done INT DEFAULT 0(仅用于游标结束),DECLARE bottle_code VARCHAR(50)(接收子查询结果) - 每次进入循环体前,把可能被污染的变量清空:
SET bottle_code = NULL - 子查询后立刻重置
done:SET done = 0(否则上一次SELECT INTO的副作用还在)
示例片段:
DECLARE done INT DEFAULT 0; DECLARE _PackCode VARCHAR(50); DECLARE _BottlePackCode VARCHAR(50); DECLARE cur CURSOR FOR SELECT PackCode FROM acexeym_packcode.code WHERE orderid = _orderid; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; <p>OPEN cur; read_loop: LOOP FETCH cur INTO _PackCode; IF done = 1 THEN LEAVE read_loop; END IF;</p><p>SET _BottlePackCode = NULL; -- 清空,避免残留值干扰后续 IS NULL 判断 SELECT PackCode INTO _BottlePackCode FROM acexeym_packcode.code WHERE ParentCode = _PackCode AND ActiveTime IS NULL LIMIT 1;</p><p>SET done = 0; -- 关键!重置,防止上面 SELECT 触发的 NOT FOUND 影响游标逻辑</p><p>IF _BottlePackCode IS NOT NULL THEN UPDATE acexeym_packcode.code SET status = 1 WHERE PackCode = _BottlePackCode; END IF; END LOOP; CLOSE cur;</p>
SQLSTATE '02000' 和 NOT FOUND 有什么区别?
二者等价,都代表“没有更多行”——NOT FOUND 是 MySQL 对 SQLSTATE '02000' 的别名写法,语义和行为完全一致。选哪个纯属风格偏好,但要注意:
- 不要混用:
DECLARE CONTINUE HANDLER FOR NOT FOUND和DECLARE CONTINUE HANDLER FOR SQLSTATE '02000'效果一样,但不能同时声明两个 - 它们只响应“无数据”类信号,对真正的错误(如主键冲突
1062、表不存在1146)完全不捕获 - 若需同时处理游标结束和业务错误,得额外加
EXIT HANDLER FOR SQLEXCEPTION,且顺序要放在游标声明之后、OPEN之前
真正容易被忽略的是:NOT FOUND 处理器一旦声明,就对块内所有 SELECT ... INTO 生效,而不仅限于游标 FETCH。不手动重置标志位,游标逻辑就会被内部查询悄悄破坏。











