在編寫稍微複雜的MySQL存儲過程時,游標的使用看似簡單,實則暗藏幾個極易踩坑的細節。本文分享兩個實戰中常被忽略的「坑點」,助你避開不必要的調試時間。
假設需求是:從表 t 遍歷字段 f,依序到表 t2 查找符合條件的記錄並輸出。典型代碼結構如下:
DECLARE stopFlag INT DEFAULT 0;
DECLARE cur_name CURSOR FOR SELECT f FROM t;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET stopFlag = 1;
OPEN cur_name;
REPEAT
FETCH cur_name INTO temp_f;
BEGIN
SELECT f3 INTO my_result FROM t2 WHERE f2 = temp_f LIMIT 1;
END;
UNTIL stopFlag = 1
END REPEAT;
CLOSE cur_name;
現象:表 t 有 n 條記錄,邏輯上應循環 n 次,實際卻執行了 n+1 次。
根因:當 FETCH 讀取到最後一行時,stopFlag 仍為 0。直到下一次 FETCH 觸發 NOT FOUND 才將其置 1,但此時循環體 BEGIN...END 已進入,且缺乏中途判斷,導致多執行一次。這多出來的一次 temp_f 值與上一輪相同。
解決方案:在 BEGIN 內部緊接著判斷 stopFlag,若為 1 則 LEAVE 或跳過後續邏輯:
REPEAT
FETCH cur_name INTO temp_f;
IF stopFlag = 1 THEN
LEAVE loop_label; -- 或用 IF 包裹後續邏輯
END IF;
BEGIN
SELECT f3 INTO my_result FROM t2 WHERE f2 = temp_f LIMIT 1;
END;
UNTIL stopFlag = 1
END REPEAT;
現象:明明還有數據,循環卻提前結束,未遍歷完所有 n 行。
根因:循環體內的 SELECT ... INTO my_result ... 若在 t2 中找不到匹配行,同樣會觸發 NOT FOUND,進而將 stopFlag 設為 1。隨後 UNTIL 判斷滿足條件,循環被迫中止。
解決方案:在進入循環體時備份 stopFlag,退出前恢復,隔離內部查詢對控制變量的副作用:
REPEAT
FETCH cur_name INTO temp_f;
IF stopFlag = 1 THEN LEAVE loop_label; END IF;
SET @saved_flag = stopFlag; -- 備份
BEGIN
SELECT f3 INTO my_result FROM t2 WHERE f2 = temp_f LIMIT 1;
END;
SET stopFlag = @saved_flag; -- 恢復
UNTIL stopFlag = 1
END REPEAT;
游標循環的核心在於狀態機的精確控制。記住兩點:
SELECT ... INTO,務必隔離其對 NOT FOUND 處理器的影響。掌握這兩點,存儲過程中的游標邏輯將更健壯、可控。