2012-02-16 144 views
7

我在這裏問Using cursor in OLTP databases (SQL server)優點和使用遊標(在SQL Server)的利弊

一個問題,人們迴應說光標不應該被使用。

我覺得光標是非常強大的工具,我打算使用它(我不認爲微軟支持壞的開發者的遊標)。假設你有一個表格,其中一列中的值取決於值在上一行中的同一列。如果這是一次後端進程,你不覺得使用遊標將是一個可以接受的選擇嗎?

關閉我的頭頂我可以想到幾個場景,我覺得在使用遊標時不應該感到羞恥。如果你們有其他感覺,請告訴我。

1>一次性後臺進程清理在幾分鐘內完成執行的錯誤數據。 2>長時間運行一次的批處理過程(類似於每年一次)。 如果在上面的場景中,其他進程沒有明顯的壓力,那麼花費額外的時間編寫代碼來避免遊標是不合理的嗎?換句話說,在某些情況下,開發人員的時間比對其他任何事物幾乎沒有影響的進程的性能更重要。

在我看來,這些應該是你應該認真嘗試避免使用遊標的場景。 1>從網站上調用的一個存儲過程,可以經常被調用。 2>一天運行多次並佔用大量資源的SQL作業。

我認爲,如果不分析手邊的任務並實際衡量其他選擇,就會形成一個像「不應該使用遊標」這樣的通用語句。

請讓我知道你的想法。

+1

看起來像Sql Server 2012將支持以基於集合的方式從前一行獲取值的情況。 – automatic 2012-02-16 23:25:55

+1

「從網站調用的存儲過程」太含糊。如果光標是解決問題的最合理選擇,並且必須從網頁調用該過程... – 2012-02-17 04:37:21

回答

11

有幾種情況下游標實際上比基於集合的等價物執行得更好。總是想到運行總計 - 請查看Itzik的話(並且忽略涉及SQL Server 2012的任何內容,它增加了新的窗口函數,讓遊標在這種情況下能夠運行)。

人們用光標遇到的一個大問題是,它們的執行速度很慢,它們使用臨時存儲等。這部分是因爲默認語法是一個帶有各種低效默認選項的全局光標。當你下一次使用不需要像UPDATE...WHERE CURRENT OF(我已經能夠避免我整個職業生涯)那樣的事情做一些事情時,通過比較這兩種語法選項來給它一個公平的動搖:

DECLARE c CURSOR 
    FOR <SELECT QUERY>; 

DECLARE c CURSOR 
    LOCAL STATIC READ_ONLY FORWARD_ONLY 
    FOR <SELECT QUERY>; 

實際上,第一個版本表示未公開的存儲過程sp_MSforeachdb中的一個錯誤,如果任何數據庫的狀態在執行期間發生更改,則它會跳過數據庫。隨後,我編寫了我自己的存儲過程版本(請參閱herehere),這兩個版本都修復了這個錯誤(僅使用上述語法的後一版本),並添加了幾個參數來控制將選擇哪些數據庫。

很多人認爲方法不是遊標,因爲它不會說DECLARE CURSOR。我曾經看到有人認爲while循環比光標(which I hope I've dispelled here)更快,或者使用FOR XML PATH來執行組級聯並不執行隱藏的遊標操作。在很多案例中查看計劃將顯示事實。

在很多情況下,使用基於集合的更合適的遊標。但是有很多有效的用例,其中基於集合的等價物寫得複雜得多,因爲優化器可以爲兩者或者不可能生成計劃(例如,通過循環遍歷表以更新統計信息的維護任務,爲結果中的每個值調用存儲過程等)。對於許多大型多表查詢而言,情況也是如此,因爲該計劃對於優化器來說太過分了。在這些情況下,最好先將一些中間結果轉儲到臨時結構中。對於某些基於集合的遊標(例如運行總計)也是如此。我也寫過另一種方式,人們幾乎總是本能地使用while循環/光標,並且有clever set-based alternatives that are much better

UPDATE 2013年7月25日

只是想添加一些額外的博客文章我已經寫光標,你應該使用哪些選項,如果你有使用它們,並使用基於集合查詢,而不是環路,產生集:

Best Approaches for Running Totals - Updated for SQL Server 2012

What impact can different cursor options have?

生成一組或序列沒有循環:[Part 1][Part 2][Part 3]

6

SQL Server中游標的問題在於引擎是在內部基於set的,與其他DBMS類似於Oracle的內部基於遊標的Oracle不同。這意味着當您在SQL Server中創建遊標時,需要創建臨時存儲,並且需要將基於集合的結果集複製到臨時遊標存儲中。你可以看到爲什麼這樣做會很昂貴,更不用說你可能在光標本身之上進行的任何逐行處理。底線是基於集合的處理更高效,並且通常使用CTE或臨時表可以更好地完成基於光標的操作。

這就是說,有些情況下光標可能是可以接受的,就像你說的一次性操作一樣。我能想到的最常見的用途是在維護計劃中,您可能正在遍歷執行各種維護任務的服務器上的所有數據庫。只要你限制你的使用並且不要圍繞RBAR(逐行處理)設計整個應用程序,你應該沒問題。

3

通常遊標是一件壞事。但是在某些情況下,使用遊標更實用,而在某些情況下,使用遊標更爲實用。一個很好的例子就是通過聯繫人表格按照某些標準發送電子郵件。 (如果從DBMS發送電子郵件是一個好主意,不要打開這個問題 - 讓我們假設它是針對手頭的問題。)沒有辦法編寫基於集合的。您可以使用一些技巧來創建一個基於集合的解決方案來生成動態SQL,但不存在真正的基於集合的解決方案。

但是,涉及上一行的計算可以使用自連接完成。這通常比遊標快。

在所有情況下,您需要平衡開發更快解決方案所需的努力。如果沒有人關心,如果你在1分鐘或1小時內處理運行,使用最快的工作。如果你正在遍歷一個像[orders]表一樣隨時間增長的數據集,儘量遠離遊標。如果您不確定,請在幾種明顯不同的數據大小上進行性能測試,比較遊標基礎和基於集合的解決方案。

+1

+1我同意你說過的很多內容,儘管之前的行計算可能會非常複雜使用大多數基於集合的解決方案進行編寫,例如運行總計(現在丟棄預發佈功能)。 – 2012-02-17 04:34:27

0

它們對於像動態SQL旋轉這樣的東西是必需的,但是您應該儘量避免使用它們。

0

我一直不喜歡遊標,因爲它們的性能很差。但是,我發現我沒有完全理解不同類型的遊標,並且在某些情況下,遊標是一個可行的解決方案。

如果您遇到的業務問題只能通過一次處理一行來解決,那麼光標是適當的。

所以,爲了提高遊標的性能,請更改您使用的遊標類型。我不知道的是,如果你沒有指定你正在聲明的是哪種類型的遊標,默認情況下你會得到Dynamic Optimistic類型,這是性能最慢的類型,因爲它在引擎蓋下做了很多工作。但是,通過將遊標聲明爲不同類型,例如靜態遊標,它具有非常好的性能。

請參閱以下文章一個更全面的解釋:

The Truth About Cursors: Part I

The Truth About Cursors: Part II

The Truth About Cursors: Part III

我覺得對光標的最大con是性能,但是,在沒有鋪設任務基於集合的方法可能排在第二位。第三是可讀性和任務佈局,因爲他們通常沒有很多有用的評論。

SQL Server針對運行基於集合的方法進行了優化。您編寫查詢以返回數據結果集,例如SQL Server執行引擎確定要使用哪個連接:合併連接,嵌套循環連接或散列連接。 SQL Server根據參與的列,數據量,索引結構和參與列中的值集合確定最佳加入算法。因此,使用基於集合的方法通常是在程序光標方法上性能最好的方法。