游標是存儲過程中逐行處理結果集的機制,類似指針或數組下標。雖然功能強大,但逐行處理效率低、增加服務器負擔,生產環境能用集合操作 (JOIN、子查詢、窗口函數) 解決的堅決不用游標。
CREATE PROCEDURE proc_name()
BEGIN
-- 1. 變量聲明 (必須在游標之前)
DECLARE var_name data_type [DEFAULT value];
-- 2. 條件/處理器聲明 (可選,但在游標之前)
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- 3. 游標聲明 (在變量/處理器之後,業務邏輯之前)
DECLARE cursor_name CURSOR FOR SELECT_statement;
-- 4. 業務邏輯
OPEN cursor_name;
FETCH cursor_name INTO var_list;
WHILE done <> 1 DO
-- 處理當前行
FETCH cursor_name INTO var_list;
END WHILE;
CLOSE cursor_name;
END
鐵律:聲明順序 — 變量 → 條件/處理器 → 游標 → 業務代碼。違序報錯。
| 命令 | 作用 |
|---|---|
DECLARE c CURSOR FOR SELECT ... | 定義游標關聯的結果集 (不執行查詢) |
OPEN c | 執行查詢,建立結果集,指針指向第一行之前 |
FETCH c INTO var1, var2... | 移動指針到下一行,將列值賦給變量。無數據時觸發 NOT FOUND |
CLOSE c | 釋放游標佔用的資源,**必須顯式關閉** |
DECLARE done INT DEFAULT 0;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur;
FETCH cur INTO v1, v2;
WHILE done <> 1 DO
-- 處理 v1, v2
FETCH cur INTO v1, v2;
END WHILE;
CLOSE cur;
OPEN cur;
read_loop: LOOP
FETCH cur INTO v1, v2;
IF done = 1 THEN LEAVE read_loop; END IF;
-- 處理
END LOOP;
CLOSE cur;
OPEN cur;
REPEAT
FETCH cur INTO v1, v2;
IF done <> 1 THEN
-- 處理
END IF;
UNTIL done = 1 END REPEAT;
CLOSE cur;
DELIMITER $$
CREATE PROCEDURE count_high_salary(OUT total INT)
BEGIN
DECLARE v_sal INT;
DECLARE done INT DEFAULT 0;
DECLARE cur CURSOR FOR SELECT sal FROM employee WHERE sal IS NOT NULL;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
SET total = 0;
OPEN cur;
FETCH cur INTO v_sal;
WHILE done <> 1 DO
IF v_sal > 999 THEN
SET total = total + 1;
END IF;
FETCH cur INTO v_sal;
END WHILE;
CLOSE cur;
END$$
DELIMITER ;
-- 調用
CALL count_high_salary(@cnt);
SELECT @cnt; -- 結果: 13
游標不支持動態 SQL (無法用準備語句定義)。若需動態,只能在應用層拼裝 SQL 後調用存儲過程,或用臨時表中轉。
游標是 MySQL 存儲過程的「最後手段」。掌握聲明順序、處理器機制、三種循環寫法、必須 CLOSE,但生產代碼中應極力避免。遇到「必須逐行處理」的場景,先思考:能否用 ROW_NUMBER()、LAG/LEAD、JSON_TABLE、INSERT ... SELECT 等集合化方式解決?