首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> SQL Server 資料庫 >> 關於SqlServer

SQL 游標 完整指南:類型、語法、效能陷阱與集合思維替代方案

     編輯:關於SqlServer

什麼是游標?

游標 是資料庫引擎在記憶體中建立的**結果集指標**,允許逐列處理查詢結果。SQL 本質為**集合導向語言** (Set-based),單一陳述式處理整個集合;游標將集合操作**序列化**為逐列迴圈,違背關聯模型設計初衷。

游標分類

維度類型特性
游標範圍SELECT 游標最常用,對應 DECLARE CURSOR FOR SELECT ...
INSERT 游標對應 DECLARE CURSOR FOR INSERT ... (ESQL/C 等嵌入式 SQL),T-SQL 不直接支援。
UPDATE/DELETE 游標定位更新/刪除:WHERE CURRENT OF cursor_name,需 FOR UPDATE 宣告。
資料庫游標 vs 語言游標資料庫游標 (Server-side,T-SQL DECLARE CURSOR) vs 語言游標 (Client-side,ODBC/ADO/ADO.NET SqlDataReader 等)。
捲動能力FORWARD_ONLY (預設)僅能 FETCH NEXT 向前,效能最好、資源最少。
SCROLL支援 FETCH FIRST/LAST/PRIOR/NEXT/ABSOLUTE n/RELATIVE n,需臨時表儲存結果集。
STATIC建立時複製結果集至 tempdb,隔離基礎資料變更,唯讀。
KEYSET固定鍵值集合,非鍵值欄位變更可見,鍵值變更不可見 (跳過)。
DYNAMIC完全反映基礎資料所有變更,開銷最大。
並行控制READ_ONLY不可透過游標更新,允許最佳化器選擇較輕量計畫。
OPTIMISTIC更新時檢查時間戳/版本,衝突則報錯。
SCROLL_LOCKS (FOR UPDATE)讀取時加 U/X 鎖,保證更新成功,並發度最低。
區域性LOCAL (預設) / GLOBALLOCAL 僅批次/程序內可見,GLOBAL 連線層級可見 (慎用)。

T-SQL 游標標準語法


DECLARE cursor_name CURSOR
    [ LOCAL | GLOBAL ]
    [ FORWARD_ONLY | SCROLL ]
    [ STATIC | KEYSET | DYNAMIC | FAST_FORWARD ]  -- FAST_FORWARD = FORWARD_ONLY + READ_ONLY + 效能優化
    [ READ_ONLY | SCROLL_LOCKS | OPTIMISTIC ]
    [ TYPE_WARNING ]
FOR select_statement
[ FOR UPDATE [ OF column_list ] ]

OPEN cursor_name

FETCH [ NEXT | PRIOR | FIRST | LAST | ABSOLUTE n | RELATIVE n ]
FROM cursor_name
INTO @Var1, @Var2, ...

WHILE @@FETCH_STATUS = 0
BEGIN
    -- 逐列處理邏輯
    FETCH NEXT FROM cursor_name INTO @Var1, @Var2, ...
END

CLOSE cursor_name
DEALLOCATE cursor_name

效能陷阱:為何游標是「效能殺手」

  1. 逐列 = 逐列執行計畫:1 萬列游標 = 1 萬次查詢編譯/執行/鎖定/網路往返。集合操作單一計畫處理所有列。
  2. 鎖持有久、粒度粗:SCROLL_LOCKS 長時間持有 U/X 鎖;STATIC 複製至 tempdb 消耗 I/O。
  3. 記憶體壓力:伺服器端游標佔用連線記憶體、tempdb (STATIC/KEYSET)、鎖管理員資源。
  4. 平行度喪失:游標強制序列執行,無法利用多核平行查詢。
  5. 統計資訊盲區:最佳化器無法預估游標內部邏輯成本,計畫往往次優。

實戰對比:游標 vs 集合操作


-- ❌ 游標寫法:為每位客戶更新最新訂單日期 (假設 100 萬客戶)
DECLARE c CURSOR LOCAL FAST_FORWARD FOR SELECT CustomerID FROM Customers;
OPEN c; FETCH NEXT FROM c INTO @CustID;
WHILE @@FETCH_STATUS = 0
BEGIN
    UPDATE Customers SET LastOrderDate = (SELECT MAX(OrderDate) FROM Orders WHERE CustomerID = @CustID)
    WHERE CustomerID = @CustID;
    FETCH NEXT FROM c INTO @CustID;
END
CLOSE c; DEALLOCATE c;
-- 耗時:~45 分鐘 (含鎖爭用、計畫編譯、交易記錄)

-- ✅ 集合寫法:單一 UPDATE + JOIN/子查詢
UPDATE c
SET LastOrderDate = o.MaxDate
FROM Customers c
JOIN (SELECT CustomerID, MAX(OrderDate) AS MaxDate FROM Orders GROUP BY CustomerID) o
  ON c.CustomerID = o.CustomerID;
-- 耗時:~3 秒 (平行掃描、單一計畫、最小記錄)

-- ✅ 替代方案:MERGE (同步來源/目標)
MERGE INTO Customers c
USING (SELECT CustomerID, MAX(OrderDate) AS MaxDate FROM Orders GROUP BY CustomerID) o
  ON c.CustomerID = o.CustomerID
WHEN MATCHED THEN UPDATE SET LastOrderDate = o.MaxDate;

何時「不得不」用游標?

  • **動態 SQL 批次執行**:每列參數不同的 EXEC sp_executesql (如依表名動態截斷/重建索引)。
  • **外部 API/CLR 呼叫**:每列需呼叫 Web Service、寄送郵件、啟動外部程式。
  • **複雜程序邏輯無法集合化**:遞迴階層處理、狀態機流程、逐列條件分支極多。
  • **維護腳本/一次性作業**:DBA 手工執行、資料修正、不追求效能。
即使必須用游標:**盡量用 FAST_FORWARD (唯讀、僅向前)、READ_ONLY、避免 SCROLL_LOCKS、批次大小控制、在應用層 (C#/PowerShell) 用 SqlDataReader 替代伺服器端游標。**

現代替代技術棧

場景集合/現代替代關鍵語法/工具
逐列更新/刪除單一 UPDATE/DELETE + JOIN/CTE/APPLYCROSS APPLY, OUTER APPLY, MERGE
逐列插入INSERT ... SELECT / 表值建構子VALUES (...), (...), ..., OPENJSON 解析陣列
逐列計算彙總窗格函數SUM() OVER (PARTITION BY ...), ROW_NUMBER(), LAG/LEAD
逐列動態 SQL參數化批次 / CLR / 應用層平行sp_executesql + 表值參數 (TVP), SqlBulkCopy, Parallel.ForEach
ETL 轉換SSIS / Azure Data Factory / PolyBase資料流元件、對應資料流、T-SQL 推算下推

結語

**游標是「最後手段」,而非「首選工具」。** 具備集合思維、熟練窗格函數、APPLY、MERGE、TVP、SSIS,99% 游標場景可重構為高效能集合操作。下次寫 DECLARE CURSOR 前,問自己:**「這能不能用單一 SELECT/UPDATE/DELETE/INSERT 解決?」** 答案幾乎總是肯定的。

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