首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫

MySQL 存儲過程實戰詳解:游標循環、多游標嵌套、條件處理器完整指南

     編輯:MySQL 資料庫相關知識

核心概念

  • 存儲過程:預編譯 SQL 集合,減少網絡交互、封裝邏輯、提升性能。
  • 游標:逐行處理結果集,**唯一支持行級邏輯**的機制。
  • 條件處理器:`DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1;` 監聽游標結束。
  • 循環結構:`LOOP`/`REPEAT`/`WHILE` + `LEAVE`(break) + `ITERATE`(continue)。

語法基石

DELIMITER //  -- 修改語句結束符,避免過程體內 ; 衝突
CREATE PROCEDURE proc_name(IN param INT, OUT result INT)
BEGIN
  -- 聲明區 (順序固定:變量 → 游標 → 處理器)
  DECLARE var INT DEFAULT 0;
  DECLARE cur CURSOR FOR SELECT col FROM table WHERE cond;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
  
  -- 執行區
  OPEN cur;
  read_loop: LOOP
    FETCH cur INTO var;
    IF done THEN LEAVE read_loop; END IF;
    -- 處理邏輯
  END LOOP;
  CLOSE cur;
END//
DELIMITER ;

實例一:單游標循環更新 (昨日數據匯總)

DELIMITER //
DROP PROCEDURE IF EXISTS getUserInfo//
CREATE PROCEDURE getUserInfo(IN p_date DATETIME)
BEGIN
  DECLARE v_user VARCHAR(12);
  DECLARE v_chinese, v_math INT;
  DECLARE done INT DEFAULT 0;
  
  -- 游標:查詢指定日期創建的用戶成績
  DECLARE cur CURSOR FOR 
    SELECT username, chinese, math FROM userInfo 
    WHERE DATEDIFF(createDate, p_date) = 0;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
  
  -- 參數默認值:昨天
  IF p_date IS NULL THEN SET p_date = DATE_ADD(NOW(), INTERVAL -1 DAY); END IF;
  
  OPEN cur;
  read_loop: LOOP
    FETCH cur INTO v_user, v_chinese, v_math;
    IF done THEN LEAVE read_loop; END IF;
    
    -- 業務邏輯:更新匯總表
    UPDATE infoSum SET total = v_chinese + v_math WHERE UserName = v_user;
  END LOOP read_loop;
  CLOSE cur;
END//
DELIMITER ;

實例二:三種循環結構對比

1. REPEAT ... UNTIL (後測試,至少執行一次)

DECLARE num INT DEFAULT 1;
DECLARE str VARCHAR(255) DEFAULT '';
REPEAT
  SET str = CONCAT(str, num, ',');
  SET num = num + 1;
UNTIL num > 5 END REPEAT;
-- 結果: '1,2,3,4,5,'

2. WHILE ... DO (預測試,條件滿足才執行)

DECLARE num INT DEFAULT 1;
DECLARE str VARCHAR(255) DEFAULT '';
WHILE num < 10 DO
  SET str = CONCAT(str, num, ',');
  SET num = num + 1;
END WHILE;

3. LOOP + LEAVE/ITERATE (最靈活,需標籤)

DECLARE num INT DEFAULT 1;
DECLARE str VARCHAR(255) DEFAULT '';
loop_label: LOOP
  IF num >= 10 THEN LEAVE loop_label; END IF;  -- break
  SET num = num + 1;
  IF (num % 3) = 0 THEN ITERATE loop_label; END IF;  -- continue (跳過 3 的倍數)
  SET str = CONCAT(str, num, ',');
END LOOP loop_label;
-- 結果: '2,4,5,7,8,' (跳過 3,6,9)

實例三:多游標處理 (兩種模式)

模式 A:順序處理 + 重置 `done` 變量

CREATE PROCEDURE test_proc_1()
BEGIN
  DECLARE done INT DEFAULT 0;
  DECLARE tname, tpass VARCHAR(50);
  DECLARE cur1 CURSOR FOR SELECT name, password FROM t;
  DECLARE cur2 CURSOR FOR SELECT id, name FROM t;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;

  OPEN cur1;
  REPEAT FETCH cur1 INTO tname, tpass; 
    IF NOT done THEN SELECT tname, tpass; END IF;
  UNTIL done END REPEAT;
  CLOSE cur1;

  -- 關鍵:重置 done = 0,否則第二個游標不執行!
  SET done = 0;
  OPEN cur2;
  REPEAT FETCH cur2 INTO tid, tname; 
    IF NOT done THEN SELECT tid, tname; END IF;
  UNTIL done END REPEAT;
  CLOSE cur2;
END

模式 B:BEGIN...END 塊嵌套 (各自獨立 `done` 變量,更安全)

CREATE PROCEDURE test_proc_2()
BEGIN
  -- 第一個游標塊
  BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE tname, tpass VARCHAR(50);
    DECLARE cur1 CURSOR FOR SELECT name, password FROM t;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    OPEN cur1;
    REPEAT FETCH cur1 INTO tname, tpass; 
      IF NOT done THEN SELECT tname, tpass; END IF;
    UNTIL done END REPEAT;
    CLOSE cur1;
  END;

  -- 第二個游標塊 (獨立變量作用域)
  BEGIN
    DECLARE done INT DEFAULT 0;
    DECLARE tid INT; DECLARE tname VARCHAR(50);
    DECLARE cur2 CURSOR FOR SELECT id, name FROM t;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
    OPEN cur2;
    REPEAT FETCH cur2 INTO tid, tname; 
      IF NOT done THEN SELECT tid, tname; END IF;
    UNTIL done END REPEAT;
    CLOSE cur2;
  END;
END

實例四:完整測試表與調用

-- 測試表
DROP TABLE IF EXISTS netingcn_proc_test;
CREATE TABLE netingcn_proc_test (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(20), password VARCHAR(20)
) ENGINE=InnoDB;
INSERT INTO netingcn_proc_test (name, password) VALUES
('proc1','p1'),('proc2','p2'),('proc3','p3'),('proc4','p4');

-- 調用
CALL test_proc();      -- 單游標
CALL test_proc_1();    -- 多游標順序
CALL test_proc_2();    -- 多游標嵌套

避坑指南 (血淚總結)

坑點後果正確姿勢
聲明順序錯誤 (游標前聲明變量,處理器最後)語法錯誤**變量 → 游標 → 處理器** 嚴格遵守
FETCH 目標變量名與游標列名同名變量值為 NULL (列名遮蔽)變量名加前綴 `v_`/`p_`,避免同名
多游標不重置 `done` / 不用嵌套塊第二個游標不執行模式 A: `SET done=0`;模式 B: `BEGIN...END` 隔離作用域
游標 SELECT 列數/類型與 FETCH 變量不匹配數據截斷/報錯嚴格對應,或用 `SELECT *` 配合定義完整記錄變量 (需 ROW 類型支持)
忘記 `CLOSE cursor`資源洩漏、報錯 "Cursor is not open"必配 `OPEN`/`CLOSE`,異常處理中也要 CLOSE
在游標循環中修改同表數據可能觸發 "Table is read only" 或死鎖避免同表讀寫衝突,或用臨時表緩存 ID 後批量更新

最佳實踐建議

  1. 能用單 SQL (JOIN/子查詢/窗口函數) 解決,絕不用游標:集合操作比行迭代快數量級。
  2. 複雜 ETL/報表/遷移**離線批量**任務:適合存儲過程 + 游標封裝邏輯。
  3. 業務核心邏輯留在應用層 (Java/Go/Python):版本控制、測試、調試、重構友好;數據庫只做數據存取。
  4. 命名規範:`sp_業務_動作` (如 `sp_order_cancel`),參數 `p_`、變量 `v_`、游標 `cur_`、標籤 `lbl_`。
  5. 權限控制:`GRANT EXECUTE ON PROCEDURE db.sp_xxx TO 'app'@'%';` 僅授予執行權。

總結

MySQL 存儲過程 + 游標是處理**行級邏輯**的利器,核心在於:**聲明順序固定、處理器捕獲 NOT FOUND、循環標籤配合 LEAVE/ITERATE、多游標隔離作用域**。但**優先考慮集合操作 (SQL)**,存儲過程僅作為補充工具用於特定批量/遷移場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved