雖然現代開發多建議在應用層(PHP、Python、Java)處理迴圈邏輯,但在以下場景必須或宜在資料庫層遍歷:
MySQL 游標(Cursor)是唯一在存儲程序中逐行處理結果集的方式。特性:
DECLARE 區塊定義,搭配 OPEN、FETCH、CLOSENOT FOUND 條件處理器避免迴圈異常終止DELIMITER $$
CREATE PROCEDURE `MakeFriends`()
BEGIN
-- 1. 宣告變數
DECLARE v_adminId BIGINT(12) DEFAULT 1;
DECLARE v_currentUserId BIGINT(12);
DECLARE v_existRel INT;
DECLARE v_done INT DEFAULT 0;
-- 2. 宣告游標:查詢所有用戶 ID
DECLARE user_cursor CURSOR FOR
SELECT `userId` FROM `hw_user`;
-- 3. 宣告 NOT FOUND 處理器:游標讀完時將 v_done 設為 1
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done = 1;
-- 4. 開啟游標
OPEN user_cursor;
-- 5. 讀取第一筆
FETCH NEXT FROM user_cursor INTO v_currentUserId;
-- 6. 迴圈遍歷
read_loop: REPEAT
IF NOT v_done THEN
-- 檢查是否已存在關係(避免重複插入)
SELECT COUNT(*)
INTO v_existRel
FROM `hw_relationship`
WHERE `sourceId` = v_currentUserId
AND `targetId` = v_adminId;
-- 非管理員本身且關係不存在時,建立雙向關係
IF v_currentUserId != v_adminId AND v_existRel = 0 THEN
INSERT INTO `hw_relationship` (`createTime`, `relationType`, `sourceId`, `targetId`)
VALUES (NOW(), 1, v_currentUserId, v_adminId);
INSERT INTO `hw_relationship` (`createTime`, `relationType`, `sourceId`, `targetId`)
VALUES (NOW(), 1, v_adminId, v_currentUserId);
END IF;
-- 讀取下一筆
FETCH NEXT FROM user_cursor INTO v_currentUserId;
END IF;
UNTIL v_done END REPEAT;
-- 7. 關閉游標
CLOSE user_cursor;
END$$
DELIMITER ;
| 語句 | 說明 |
|---|---|
DECLARE cur CURSOR FOR SELECT ... | 定義游標,綁定 SELECT 查詢(不可含 INTO) |
OPEN cur | 執行查詢,建立結果集指標 |
FETCH NEXT FROM cur INTO var1, var2... | 取出下一行,將欄位值存入變數 |
CLOSE cur | 釋放游標資源 |
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1 | 當 FETCH 無資料時,不拋出錯誤,改設旗標 |
若僅需按主鍵順序處理且 ID 連續/可預測:
CREATE PROCEDURE `BatchProcess`()
BEGIN
DECLARE v_max_id INT;
DECLARE v_cur_id INT DEFAULT 1;
SELECT MAX(`id`) INTO v_max_id FROM `target_table`;
WHILE v_cur_id <= v_max_id DO
-- 處理單筆
UPDATE `target_table` SET `status` = 2 WHERE `id` = v_cur_id;
SET v_cur_id = v_cur_id + 1;
END WHILE;
END
缺點:ID 不連續會空轉、無法處理複雜查詢結果集、效能通常不如集合操作。
核心原則:能用單一 SQL 解決(集合操作),絕不寫迴圈(逐列處理)。
-- 想把所有 status=1 的改為 2
DECLARE cur CURSOR FOR SELECT id FROM t WHERE status=1;
-- 迴圈 UPDATE ... WHERE id = v_id; -- 每次 UPDATE 都有網路往返、鎖開銷
UPDATE `t` SET `status` = 2 WHERE `status` = 1; -- 單條 SQL 完成
INSERT ... SELECT、UPDATE ... JOIN、CASE WHEN、窗口函數COMMIT 一次,避免長事務LIMIT offset, size),應用層並行處理,再批次寫回SHOW PROCESSLIST 觀察 Time 欄位,設定 max_execution_timeMySQL 游標是處理逐列邏輯的標準工具,但90% 的遍歷需求可用集合操作替代並獲得數量級效能提升。掌握游標語法是為了知道「何時不該用它」,真正的高效能來自集合思維與索引設計。