設計優良、效能卓越的資料庫引擎可以輕鬆地同時為成千上萬的使用者服務。而“底氣不足”的資料庫系統隨著更多的使用者同時訪問系統將大大降低其效能。最糟糕的情況下甚至可能導致系統的崩潰。
當然,並行訪問是任何資料庫解決方案都最為重視的問題了,為瞭解決並行訪問方面的問題各類資料庫系統提出了各種各樣的方案。SQL Server和Oracle兩大DBMS也分別採用了不同的平行處理方法。它們之間的實質差別在哪裡呢?
並行訪問的問題
並行訪問出現問題存在若干種情況。在最簡單的情形下,數量超過一個的使用者可能同時查詢同一資料。就這種情況而言資料庫的操作目標很簡單:儘可能地為使用者們提供快速的資料訪問。這對我們現在常見的資料庫來說不成問題:SQL Server和Oracle都採用了多線程機制,它們當然能夠一次處理多個請求。
不過,在使用者修改資料的情況下並行訪問問題就變得複雜起來了。顯然,資料庫通常只允許唯一使用者一次修改特定的資料。當某一使用者開始修改某塊資料時, SQL Server和Oracle都能很快地鎖定資料,阻止其他使用者對這塊資料進行更新,直到修改該資料的第1位使用者完成其操作並提交交易(commit transaction)。但是,當某一位使用者正在修改某塊資料時假設另一位使用者又正想查詢該資料的資訊時會發生什麼情況呢?在這種情況下資料庫管理系統又該如何動作呢?Oracle和SQL Server針對這一問題採取了不同的解決方案。
SQL Server方法
現在不妨假設有人開始修改SQL Server上儲存的資料,於是這塊資料立即被資料庫鎖定。資料鎖定操作阻塞其他任何訪問該資料的串連——連查詢操作都不會放過。於是,這塊被鎖定的資料只有在交易被提交或者復原之後才能接受其他訪問操作。
下面用SQL Server隨帶的pubs樣本資料庫做一個簡單示範。在Query Analyzer內開啟兩個視窗。在第1個視窗中執行下列SQL動作陳述式,更新pubs資料庫中某一圖書的價格:
以下為引用的內容: use pubs go begin tran update titles set price = price * 1.05 where title_id = 'BU2075' |
由於代碼中並沒有執行commit語句,所以資料變動操作實際上還沒有最終完成。接下來,在另一個視窗裡執行下列語句查詢titles資料表:
select title_id,title,price from titles order by title_id.
你什麼結果也得不到。視窗底部的小地球表徵圖會轉個不停。儘管我在先前的操作中僅僅更新了一行,但是,select語句的執行對象卻恰好包含了其資料正被修改的一行。因此,上面的操作不會返回任何資料,除非回到第1個視窗提交交易或者復原。
SQL Server的資料鎖定方案可能會降低系統的效能和效率。資料被鎖定的時間越長,或者鎖定的資料量越大,其他資料訪問使用者就越可能不得不等待其查詢語句的執行。因此,從程式員的角度來看,對SQL Server編程的時候應該盡量地把交易代碼設計得既小又快。
在SQL Server的最近版本中,微軟對SQL Server進行了某些修改,使其一次鎖定的資料量大大減少,這是資料庫設計中的一大重要改進。在6.5版及以前版本中,最少的資料鎖定量是一頁。哪怕你只在修改一行資料,而該行資料位元於包含10行資料的一頁上,則整頁10行資料都會被鎖定。顯然,這麼大的資料鎖定量增加了其他資料訪問串連不得不等待資料修正完成的機率。在SQL Server 7中,微軟引入了行鎖定技術,這樣,目前的SQL Server只鎖定實際正被改變的資料行。
SQL Server的解決方案聽起來很簡單,但實際上其幕後為提供足夠的系統高效能而採取了很多措施。例如,如果你在同時修改多行資料,SQL Server則會把資料鎖定範圍提升到頁層級乃至鎖定整個資料表,從而不必針對每一記錄跟蹤和維護各自的資料鎖。
Oracle方法
下面我們再看看Oracle資料庫是如何實施類似操作的。首先,我開啟一個SQLPlus執行個體執行下列查詢語句(這個例子可以在Oracle 9i中樣本中找到)。這個執行個體稱做查詢執行個體:
以下為引用的內容: select first_name, last_name, salary from hr.employees where department_id = 20; |
代碼返回兩行資料,然後,再開啟另一個SQLPlus執行個體——更新執行個體來執行以下命令:
以下為引用的內容: SQL> update hr.employees 2 set salary = salary * 1.05 3 where 4 department_id = 20 5 / |
代碼執行後回複訊息稱兩行資料已被更新。
注意,以上代碼中並沒有像在SQL Server樣本那樣鍵入“begin tran”字樣的代碼。Oracle的SQLPlus隱含啟用交易(你還可以模仿SQL Server的行為,設定“autocommit to on”自動地提交交易)。接下來我們在SQLPlus更新執行個體中再執行同查詢執行個體一樣的select語句。
結果清楚地表明:Michael和Pat的薪水都增加了,然而這個時候我還沒有提交資料變更交易。Oracle不需要使用者等待資料更新執行個體中操作被提交,它徑直返回Michael和Pat的查詢資訊,但實際上返回的是資料更新開始之前的資料檢視!
這時候,熟悉SQL Server的人可能會說了,在查詢中設定(NOLOCK)不也能達到同樣的效果嗎?可是,對SQL Server而言,在資料映像之前是不能擷取資料的。指定(NOLOCK)實際上只是得到了沒有提交的資料。Oracle的方法則提供了資料的一致視圖,所有的資訊都是針對交易的、基於儲存資料快照的。
如果在SQLPlus的更新執行個體中提交更新交易在查詢執行個體中就能看到薪水資料發生變化。如果在查詢執行個體中重新運行先前的查詢語句,那麼Oracle將返回新的薪水數值。
儲存資料快照
說了半天,在給使用者顯示先前版本的資料的同時,Oracle是如何允許其他使用者修改資料的呢?其實,只要某一使用者啟動了一宗修改資料的交易,之前的資料映像就會被寫到一個特殊的儲存地區。這種“前映像”用來向任何查詢資料的使用者提供一致的資料庫檢視。這樣,當其他使用者在修改資料的時候,在以上的測試中我們就能看到尚未發生變更的薪金資料。
這個特殊的儲存地區在哪裡呢?這個問題的答案就跟你正在使用的Oracle版本有關了。在 Oracle 8i及其以前版本中會為這一目的建立特殊的復原段。然而,這種舉措會給資料庫管理員(DBA)帶來管理和調整資料區段的工作負擔。例如,DBA必須確定為此需要的資料區段的數量以及大小等。假如復原段沒有正確配置,那麼對交易而言它們就可能不得不排隊等待復原段中出現必要的資料空間。
Oracle 9i就不同了,這是Oracle的最新版本,Oracle實現了一種新特性,這就是所謂的undo資料表空間,它有效地消除了以上的管理複雜性。雖然復原段仍然可以繼續使用,但是,DBA現在可以選擇建立undo資料表空間的方式令Oracle自己管理“前映像”的複雜空間分配。
Oracle的這種方法對程式員具有重要意義。因為復原空間不是無限的,所以,更新交易的資料快照會取代先前交易的映像。因此,如果必要的復原段被其他交易的映像覆蓋的話。已耗用時間較長的查詢操作就可能產生“ snapshot too old”錯誤。
下面舉個可能發生的案例。假設在上午11:59的時候某位職員開始更新John Doe帳務的交易。這宗交易在下午12:01被提交。同時,下午12:00某財務經理開始查詢所有的客戶帳務報表和當月收費總計。因為客戶很多,所以這一查詢操作很費了點時間,但是不論這次操作到底執行了多久,反正它檢索出的結果就是下午12:00資料庫中存在的資料。如果包含John Doe帳務前映像的復原空間在查詢執行到該客戶名字的時候被覆蓋則查詢返回錯誤訊息。
Oracle的解決方案當然更為合理,在抽象意義上提供了相比SQL Server更佳的資料一致性。在執行Oracle查詢的時候無須擔心較長的查詢操作會鎖定重要的交易。但是,在兩種資料庫同時支援海量使用者的情況下也很難證明Oracle是否就能真正實現具體條件下的資料一致性。