SQL Story摘錄(十)————游標的應該與不應該
2024-07-21 02:09:05
供稿:網友
注冊會員,創建你的web開發資料庫,
游標概觀
相信很多delphi程序員都寫過這樣的代碼:
...
begin
mydataset.open;
mydataset.frist;
while not ( mydataset.bof or mydataset.eof) do
begin
...
end;
mydataset.close;
end;
...
很久以來,我們習慣了用這樣的代碼對數據庫返回的數據進行逐行操作。在用客戶端程序的代碼打開數據集之前,我們把它當做是一個無序集合。不過,在需要時,我們在服務器端就可以直接以行操作形式處理數據集,這就是游標。
游標的的使用方法類似前面的delphi代碼,通常有如下四步:
一、聲明游標:declare cursor,這一過程在前面的代碼中沒有體現出來。然而我們都知道,在使用一個數據集之前,我們總要定義它的種種屬性,比如數據源、sql語句、打開方式等等。在游標中,數據源一般是不用指定了(因為就在當前數據庫中,ms sql server中也可以通過sql語言來讀取異構數據源)。不過我們要為它指定一個數據集,還可以為它指定各種打開方式的設置,比如是否允許寫操作,是否可以隨機讀取等等。一般來說,數據庫系統默認的游標是只讀、單向、逐行讀取的。
二、打開游標:open cursor,一個delphi(或其它開發平臺)的數據集組件,在指定了各種必要屬性后,還一定要用一個打開指令(如前面的mydataset.open)來打開它,才能得到我們所需要的數據集,對于游標,我們也一樣需要一個open指令來打開它,才能使用。
三、操作數據:這一步驟通常包括移動當前游標、讀取當前數據、操作代碼三部分。前面設置的游標屬性,很大一部分是關于這里面所能進行什么樣操作的。比如,對于雙向游標,我們可以向前或向后一行,而隨機游標,我們甚至可以以隨機方式指定游標的操作位置,但最常見的游標,是單向、只讀的那一種。而對于當前行數據,我們能常是通過定義一些變量來讀取,或移動到適當位置再進行寫操作,這一點和一般的開發工具是一樣的。至于操作代碼,這雖然不是sql的強項,但一般的數據庫系統也提供基本的過程化編碼能力,可以讓我們完成操作。
四、關閉結果集:close cursor 做事要有始有終,在delphi中,如果忘了關閉自己打開的數據集,會帶來很多麻煩,而在數據庫系統中,如果打開一個游標沒有關上(想想吧,游標操作是要對數據上鎖的),如果有很多用戶都在執行這段有問題的代碼……所以,在游標的標準語法中,有明確的語句,用來關閉數據集,并釋放所占用的各種資源。這一點更像是oo語言中的析構函數,比delphi的數據集組件的close方法要做的事情要多一些。
以上各個步驟,sql標準都約定了相應的實現代碼。但具體到各個dbms平臺的實現,卻是大同小異。問題就在于這一點不同之處足以把人煩死。所以,我在這里不具體寫出實現方法。讀者完全可以查閱自己使用的系統所帶的幫助文件,看看自己用的數據庫是如何實現的游標,實現了哪些功能。
不合理的存在
我們可以看到,游標與sql語言的其它部分有相當大的差別。它的實現和操作很復雜,而且由于要逐行操作,完成同樣功能的情況下,它通常比集合操作要慢。差距會有多大呢?舉一個極端的例子:我曾經試驗用游標給一個表填充行號,結果執行了十二個小時都沒有完,而同樣的操作,用前面文章提到過的不等聯接,只要不到三秒鐘。我保證不是所有情況下差距都會有這么大,但這種情況的確存在。特別是當大量并發任務存在時,這種長時間的鎖定是很危險的。復雜和低效,是游標的最大缺點,僅僅為這兩點,就足以讓我們對它抱有一種謹慎的態度。而且,一般來說,需要用游標進行的操作,都可以在客戶端完成(能過所謂的宿主語言,host language)。
也許,在極端的關系模型擁護者看來,游標是一個丑惡的存在。在一個完美、優雅,以無序的集合來管理信息的體系中,我們為什么要安置一個以有序方式逐行操作信息的游標呢?然而,正如《龍槍編年史》中,伊斯塔城神圣的帕拉丁神殿,卻游蕩著有史以來最黑暗的黑衣法師費斯坦但提勒斯,在龐大嚴謹的關系數據庫中,有游標這樣的另類存在。這是創造關系世界的眾神之旨意,自有其道理。
存在即合理
游標雖有如此的缺憾,但它也有存在的價值。首先,當需要有序操作的數據集很大時,特別是最終的運算結果相對很小時,如果還要發到前臺做,對網絡資源的浪費就太大了。而且,一個很大的數據集傳過來,宿主語言也不一定能支持這么龐大的數據結構(比如delphi的vcl容器在這方面就倍受指責),這一點也限制了我們用宿主語言來擴充系統功能 (比如ms sql server和interbase本來可以寫擴展函數和擴展存儲過程)。另外,如果要大量的逐行的寫操作,與前臺交互通常效率更低。游標的確不是好方法,但沒有更好的方法時,它就是最好的方法。再就是以我的經驗,以腳本寫就的游標要比宿主語言編譯后的二制代碼的可維護性和可調試性要強。
俗世之中,是沒有絕對的黑暗和光明的。關鍵,在于我們是否正確的利用它。
正確使用游標
游標本身沒有所謂的對錯,但在使用它時,我們應先三思而后行。
很多時候,游標未必是你想像中的唯一方法。我見過太多的游標腳本,本來都可以用更簡潔高效的結構化操作完成。只要簡單語句可以達到同樣的效果,就不要用游標。《程序員》上讀到過一句話:simple is smart。這是軟件開發的真理。
游標中,顯然只讀、單向的游標速度最快,而且也不容易造成死鎖,盡可能用它吧。
在游標使用的表上,建立適當的索引,這么做帶來的效率提高會比一般的sql語句更明顯,尤其是執行寫操作的游標。
游標操作的結果集,要盡可能的小。
如果游標代碼中有大量的運算,那么考慮是不是把它分散開,放到其它服務器或客戶端。
對游標代碼要進行充分的測試和驗證,再投入使用,尤其是優化程序和穩定性。這方面不能相信系統。比如你寫一個游標,每讀一行把一個變量累加一,系統永遠也不會主動把它優化成count(*)。
有些系統可以把當前事務打開的游標保持到以后的事務中,直到顯示地關閉它。不過最好不要隨便使用它。這個功能當然看起來很酷,不過濫用它會給我們帶來無窮無盡的麻煩。你真的需要這種功能嗎?
適當的時候,把它寫成擴展存儲過程或擴展存儲函數,以二進制代碼的形式鏈接進數據庫系統。這樣做的缺點是失去了靈活性,換來的是效率的提升。
附:
以前一直不會在interbase的isql中輸入多條成批執行的語句,所以什么存儲過程、觸發器、甚至于游標,都建不起來。直到有一天,注意了一下,發現在isql中是這樣做的:
set term^;
...
^
...
^
...
set term;^
從第一行set term^;開始,isql會把用^分隔開的語句成組地發至后臺執行,直至set term;^為止。有點像ms sql server的查詢分析器的“go”。這樣,我們就可以自如地用isql編寫腳本了。