欧美成人精品手机在线观看_69视频国产_动漫精品第一页_日韩中文字幕网 - 日本欧美一区二区

LOGO OA教程 ERP教程 模切知識交流 PMS教程 CRM教程 開發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

SQL Server 2000+ADO.NET實現(xiàn)并發(fā)控制

admin
2011年3月3日 16:32 本文熱度 2848
1 并發(fā)一致性問題

常見并發(fā)并發(fā)一致性問題包括:丟失的修改、不可重復(fù)讀、讀臟數(shù)據(jù)、幻影讀(幻影讀在一些資料中往往與不可重復(fù)讀歸為一類)。


1.1 丟失修改


下面我們先來看一個例子,說明并發(fā)操作帶來的數(shù)據(jù)的不一致性問題。


考慮飛機(jī)訂票系統(tǒng)中的一個活動序列:



  1. 甲售票點(甲事務(wù))讀出某航班的機(jī)票余額A,設(shè)A=16.
  2. 乙售票點(乙事務(wù))讀出同一航班的機(jī)票余額A,也為16.
  3. 甲售票點賣出一張機(jī)票,修改余額A←A-1.所以A為15,把A寫回數(shù)據(jù)庫.
  4. 乙售票點也賣出一張機(jī)票,修改余額A←A-1.所以A為15,把A寫回數(shù)據(jù)庫.

結(jié)果明明賣出兩張機(jī)票,數(shù)據(jù)庫中機(jī)票余額只減少1。


歸納起來就是:兩個事務(wù)T1和T2讀入同一數(shù)據(jù)并修改,T2提交的結(jié)果破壞了T1提交的結(jié)果,導(dǎo)致T1的修改被丟失。前文(2.1.4數(shù)據(jù)刪除與更新)中提到的問題及解決辦法往往是針對此類并發(fā)問題的。但仍然有幾類問題通過上面的方法解決不了,那就是:


1.2 不可重復(fù)讀


不可重復(fù)讀是指事務(wù)T1讀取數(shù)據(jù)后,事務(wù)T2執(zhí)行更新操作,使T1無法再現(xiàn)前一次讀取結(jié)果。具體地講,不可重復(fù)讀包括三種情況:



  • 事務(wù)T1讀取某一數(shù)據(jù)后,事務(wù)T2對其做了修改,當(dāng)事務(wù)1再次讀該數(shù)據(jù)時,得到與前一次不同的值。例如,T1讀取B=100進(jìn)行運算,T2讀取同一數(shù)據(jù)B,對其進(jìn)行修改后將B=200寫回數(shù)據(jù)庫。T1為了對讀取值校對重讀B,B已為200,與第一次讀取值不一致。
  • 事務(wù)T1按一定條件從數(shù)據(jù)庫中讀取了某些數(shù)據(jù)記錄后,事務(wù)T2刪除了其中部分記錄,當(dāng)T1再次按相同條件讀取數(shù)據(jù)時,發(fā)現(xiàn)某些記錄神密地消失了。
  • 事務(wù)T1按一定條件從數(shù)據(jù)庫中讀取某些數(shù)據(jù)記錄后,事務(wù)T2插入了一些記錄,當(dāng)T1再次按相同條件讀取數(shù)據(jù)時,發(fā)現(xiàn)多了一些記錄。(這也叫做幻影讀)

1.3 讀"臟"數(shù)據(jù)


讀"臟"數(shù)據(jù)是指事務(wù)T1修改某一數(shù)據(jù),并將其寫回磁盤,事務(wù)T2讀取同一數(shù)據(jù)后,T1由于某種原因被撤消,這時T1已修改過的數(shù)據(jù)恢復(fù)原值,T2讀到的數(shù)據(jù)就與數(shù)據(jù)庫中的數(shù)據(jù)不一致,則T2讀到的數(shù)據(jù)就為"臟"數(shù)據(jù),即不正確的數(shù)據(jù)。


產(chǎn)生上述三類數(shù)據(jù)不一致性的主要原因是并發(fā)操作破壞了事務(wù)的隔離性。并發(fā)控制就是要用正確的方式調(diào)度并發(fā)操作,使一個用戶事務(wù)的執(zhí)行不受其它事務(wù)的干擾,從而避免造成數(shù)據(jù)的不一致性。


2 并發(fā)一致性問題的解決辦法


2.2.2.1 封鎖(Locking)


封鎖是實現(xiàn)并發(fā)控制的一個非常重要的技術(shù)。所謂封鎖就是事務(wù)T在對某個數(shù)據(jù)對象例如表、記錄等操作之前,先向系統(tǒng)發(fā)出請求,對其加鎖。加鎖后事務(wù)T就對該數(shù)據(jù)對象有了一定的控制,在事務(wù)T釋放它的鎖之前,其它的事務(wù)不能更新此數(shù)據(jù)對象。


基本的封鎖類型有兩種:排它鎖(Exclusive locks 簡記為X鎖)和共享鎖(Share locks 簡記為S鎖)。


排它鎖又稱為寫鎖。若事務(wù)T對數(shù)據(jù)對象A加上X鎖,則只允許T讀取和修改A,其它任何事務(wù)都不能再對A加任何類型的鎖,直到T釋放A上的鎖。這就保證了其它事務(wù)在T釋放A上的鎖之前不能再讀取和修改A。


共享鎖又稱為讀鎖。若事務(wù)T對數(shù)據(jù)對象A加上S鎖,則其它事務(wù)只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這就保證了其它事務(wù)可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。


2.2.2.2 封鎖協(xié)議


在運用X鎖和S鎖這兩種基本封鎖,對數(shù)據(jù)對象加鎖時,還需要約定一些規(guī)則,例如應(yīng)何時申請X鎖或S鎖、持鎖時間、何時釋放等。我們稱這些規(guī)則為封鎖協(xié)議(Locking Protocol)。對封鎖方式規(guī)定不同的規(guī)則,就形成了各種不同的封鎖協(xié)議。下面介紹三級封鎖協(xié)議。三級封鎖協(xié)議分別在不同程度上解決了丟失的修改、不可重復(fù)讀和讀"臟"數(shù)據(jù)等不一致性問題,為并發(fā)操作的正確調(diào)度提供一定的保證。下面只給出三級封鎖協(xié)議的定義,不再做過多探討。



  • 1級封鎖協(xié)議

1級封鎖協(xié)議是:事務(wù)T在修改數(shù)據(jù)R之前必須先對其加X鎖,直到事務(wù)結(jié)束才釋放。事務(wù)結(jié)束包括正常結(jié)束(COMMIT)和非正常結(jié)束(ROLLBACK)。1級封鎖協(xié)議可防止丟失修改,并保證事務(wù)T是可恢復(fù)的。在1級封鎖協(xié)議中,如果僅僅是讀數(shù)據(jù)不對其進(jìn)行修改,是不需要加鎖的,所以它不能保證可重復(fù)讀和不讀"臟"數(shù)據(jù)。



  • 2級封鎖協(xié)議

2級封鎖協(xié)議是:1級封鎖協(xié)議加上事務(wù)T在讀取數(shù)據(jù)R之前必須先對其加S鎖,讀完后即可釋放S鎖。2級封鎖協(xié)議除防止了丟失修改,還可進(jìn)一步防止讀"臟"數(shù)據(jù)。



  • 3級封鎖協(xié)議

3級封鎖協(xié)議是:1級封鎖協(xié)議加上事務(wù)T在讀取數(shù)據(jù)R之前必須先對其加S鎖,直到事務(wù)結(jié)束才釋放。3級封鎖協(xié)議除防止了丟失修改和不讀'臟'數(shù)據(jù)外,還進(jìn)一步防止了不可重復(fù)讀。


2.3 事務(wù)隔離級別


盡管數(shù)據(jù)庫理論對并發(fā)一致性問題提供了完善的解決機(jī)制,但讓程序員自己去控制如何加鎖以及加鎖、解鎖的時機(jī)顯然是很困難的事情。索性絕大多數(shù)數(shù)據(jù)庫以及開發(fā)工具都提供了事務(wù)隔離級別,讓用戶以一種更輕松的方式處理并發(fā)一致性問題。常見的事務(wù)隔離級別包括:ReadUnCommitted、ReadCommitted、RepeatableRead和Serializable四種。不同的隔離級別下對數(shù)據(jù)庫的訪問方式以及數(shù)據(jù)庫的返回結(jié)果有可能是不同的。我們將通過幾個實驗深入了解事務(wù)隔離級別以及SQL Server在后臺是如何將它們轉(zhuǎn)換成鎖的。


2.3.1 ReadUnCommitted與ReadCommitted


ReadUnCommitted是最低的隔離級別,這個級別的隔離允許讀入別人尚未提交的臟數(shù)據(jù),除此之外,在這種事務(wù)隔離級別下還存在不可重復(fù)讀的問題。


ReadCommitted是許多數(shù)據(jù)庫的缺省級別,這個隔離級別上,不會出現(xiàn)讀取未提交的數(shù)據(jù)問題,但仍然無法避免不可重復(fù)讀(包括幻影讀)的問題。當(dāng)你的系統(tǒng)對并發(fā)控制的要求非常嚴(yán)格時,這種默認(rèn)的隔離級別可能無法提供數(shù)據(jù)有效的保護(hù),但對于決大多數(shù)應(yīng)用來講,這種隔離級別就夠用了。


我們使用下面的實驗來進(jìn)行測試:


首先配置SQL Server 2000數(shù)據(jù)庫,附加DBApp數(shù)據(jù)庫。然后在Visual Studio .net中建立一管理控制臺應(yīng)用程序,添加必要的命名空間引用:

using System;using System.Data;using System.Data.SqlClient;using System.Configuration;

然后建立兩個數(shù)據(jù)庫鏈接,并分別采用不同的事務(wù)隔離級別:

   private static SqlConnection conn1;private static SqlConnection conn2;private static SqlTransaction tx1;private static SqlTransaction tx2;private static void Setup(){conn1 = new SqlConnection(connectionString);conn1.Open();tx1 = conn1.BeginTransaction(IsolationLevel.ReadUncommitted);conn2 = new SqlConnection(connectionString);conn2.Open();tx2 = conn2.BeginTransaction(IsolationLevel.ReadCommitted);}

其中事務(wù)1允許讀入未提交的數(shù)據(jù),而事務(wù)2只允許讀入已提交數(shù)據(jù)。


在主程序中,我們模擬兩個人先后的不同操作,以產(chǎn)生并發(fā)一致性問題:

   public static void Main(){Setup();try{ReadUnCommittedDataByTransaction1();UnCommittedUpdateByTransaction2();ReadUnCommittedDataByTransaction1();tx2.Rollback();Console.WriteLine("\n-- Transaction 2 rollbacked!\n");ReadUnCommittedDataByTransaction1();tx1.Rollback();}catch{……}}

第一步,使用ReadUnCommittedDataByTransaction1方法利用事務(wù)1從數(shù)據(jù)庫中讀入id值為1的學(xué)生信息。此時的信息是數(shù)據(jù)庫的初始信息。


第二步,調(diào)用UnCommittedUpdateByTransaction2方法,從第2個事務(wù)中發(fā)送一UPDATE命令更新數(shù)據(jù)庫,但尚未提交。


第三步,再次調(diào)用ReadUnCommittedDataByTransaction1,從事務(wù)1中讀取數(shù)據(jù)庫數(shù)據(jù),你會發(fā)現(xiàn)由事務(wù)2發(fā)布的尚未提交的更新被事務(wù)1讀取出來(ReadUnCommitted)。


第四步,事務(wù)2放棄提交,回滾事務(wù)tx2.Rollback();。


第五步,再次調(diào)用ReadUnCommittedDataByTransaction1();,讀取數(shù)據(jù)庫中的數(shù)據(jù),此次是已經(jīng)回滾后的數(shù)據(jù)。


程序運行結(jié)果如下:

-- Read age from database:Age:20-- Run an uncommitted command:UPDATE student SET age=30 WHERE id=1-- Read age from database:Age:30-- Transaction 2 rollbacked!-- Read age from database:Age:20

關(guān)于ReadUnCommittedDataByTransaction1()與UnCommittedUpdateByTransaction2()的方法定義如下:

   private static void UnCommittedUpdateByTransaction2(){string command = "UPDATE student SET age=30 WHERE id=1";Console.WriteLine("\n-- Run an uncommitted command:\n{0}\n", command);SqlCommand cmd = new SqlCommand(command, conn2);cmd.Transaction = tx2;cmd.ExecuteNonQuery();}private static void ReadUnCommittedDataByTransaction1(){Console.WriteLine("-- Read age from database:");SqlCommand cmd = new SqlCommand("SELECT age FROM student WHERE id = 1", conn1);cmd.Transaction = tx1;try{int age = (int)cmd.ExecuteScalar();Console.WriteLine("Age:{0}", age);}catch(SqlException e){Console.WriteLine(e.Message);}}

從上面的實驗可以看出,在ReadUnCommitted隔離級別下,程序可能讀入未提交的數(shù)據(jù),但此隔離級別對數(shù)據(jù)庫資源鎖定最少。


本實驗的完整代碼可以從"SampleCode\Chapter 2\Lab 2-6"下找到。


讓我們再來做一個實驗(這個實驗要求動作要快的,否則可能看不到預(yù)期效果)。首先修改上面代碼中的Setup()方法代碼,將


tx1 = conn1.BeginTransaction(IsolationLevel.ReadUncommitted);


改為:


tx1 = conn1.BeginTransaction(IsolationLevel.ReadCommitted);


再次運行代碼,你會發(fā)現(xiàn)程序執(zhí)行到第三步就不動了,如果你有足夠的耐心等下去的話,你會看到"超時時間已到。在操作完成之前超時時間已過或服務(wù)器未響應(yīng)。"的一條提示,這條提示究竟是什么意思呢?讓我們探察一下究竟發(fā)生了什么:


第一步,在做這個實驗之前,先將SQL Server 2000的企業(yè)管理器打開,然后再將SQL Server事件探察器打開并處于探察狀態(tài)。


第二步,運行改動后的程序,程序執(zhí)行到一半就暫停了。此時迅速切換到企業(yè)管理器界面,右擊"管理"下面的"當(dāng)前活動",選擇"刷新"(整個過程應(yīng)在大約15秒內(nèi)完成即可,如圖 2-8所示),我們便得到了數(shù)據(jù)庫當(dāng)前進(jìn)程的一個快照。



 


圖 2-8 使用企業(yè)管理器查看當(dāng)前活動


我們發(fā)現(xiàn)此時進(jìn)程出現(xiàn)了阻塞,被阻塞者是52號進(jìn)程,而阻塞者是53號進(jìn)程。也就是說53號進(jìn)程的工作妨礙了52號進(jìn)程繼續(xù)工作。(不同實驗時進(jìn)程號可能各不相同)


第三步,為了進(jìn)一步查明原因真相,我們切換到事件探察器窗口,看看這兩個進(jìn)程都是干什么的。如圖 2-9所示,事件探察器顯示了這兩個進(jìn)程的詳細(xì)信息。從圖中我們可以看出,52號進(jìn)程對應(yīng)我們的事務(wù)1,53號進(jìn)程對應(yīng)我們的事務(wù)2。事務(wù)2執(zhí)行了UPDATE命令,但尚未提交,此時事務(wù)1去讀尚未提交的數(shù)據(jù)便被阻塞住。從圖中我們可以看出52號進(jìn)程是被阻塞者。


此時如果事務(wù)2完成提交,52號進(jìn)程便可以停止等待,得到需要的結(jié)果。然而我們的程序沒有提交數(shù)據(jù),因此52號進(jìn)程就要無限等下去。所幸SQL Server 2000檢測到事務(wù)2的運行時間過長(這就是上面的錯誤提示"超時時間已到。在操作完成之前超時時間已過或服務(wù)器未響應(yīng)。"),所以將事務(wù)2回滾以釋放占用的資源。資源被釋放后,52號進(jìn)程便得以執(zhí)行。



 


圖 2-9 事件探察器探察阻塞命令


第四步,了解了上面發(fā)生的事情后,我們現(xiàn)在可以深入討論一下共享鎖和排它鎖的使用情況了。重新回到企業(yè)管理器界面,讓我們查看一下兩個進(jìn)程各占用了什么資源。從圖 2-10中我們可以看出,53號進(jìn)程(事務(wù)2)在執(zhí)行更新命令前對相應(yīng)的鍵加上了排它鎖(X鎖),按照前文提到的1級封鎖協(xié)議,該排它鎖只有在事務(wù)2提交或回滾后才釋放。現(xiàn)在52號進(jìn)程(事務(wù)1)要去讀同一行數(shù)據(jù),按照2級封鎖協(xié)議,它要首先對該行加共享鎖,然而 該行數(shù)據(jù)已經(jīng)被事務(wù)2加上了排它鎖,因此事務(wù)1只能處于等待狀態(tài),等待排它鎖被釋放。因此我們就看到了前面的"阻塞"問題。



 


圖 2-10 進(jìn)程執(zhí)行寫操作前首先加了排它鎖



 


 


圖 2-11 進(jìn)程讀操作前要加共享鎖,但被阻塞


 


當(dāng)事務(wù)1的事務(wù)隔離級別是ReadUnCommitted時,讀數(shù)據(jù)是不加鎖的,因此排它鎖對ReadUnCommitted不起作用,進(jìn)程也不會被阻塞,不過確讀到了"臟"數(shù)據(jù)。


2.3.2 RepeatableRead


RepeatableRead是指可重復(fù)讀,它的隔離級別要比ReadCommitted級別高。它允許某事務(wù)執(zhí)行重復(fù)讀時數(shù)據(jù)保持不變,但是仍然無法解決幻影讀的問題。為了更深入的了解RepeatableRead所能解決的問題,我們還是使用下面的實驗來加以印證:


第一步,事務(wù)1與事務(wù)2同時設(shè)置為ReadCommitted,并同時開啟事務(wù)。


 

private static void Setup(){conn1 = new SqlConnection(connectionString);conn1.Open();tx1 = conn1.BeginTransaction(IsolationLevel.ReadCommitted);conn2 = new SqlConnection(connectionString);conn2.Open();tx2 = conn2.BeginTransaction(IsolationLevel.ReadCommitted);}

第二步,事務(wù)1讀取數(shù)據(jù)庫中數(shù)據(jù)。注意此時并沒有通過提交或回滾的方式結(jié)束事務(wù)1,事務(wù)1仍然處于活動狀態(tài)。

private static int ReadAgeByTransaction1(){return (int)ExecuteScalar("SELECT age FROM student WHERE (id = 1)");}private static object ExecuteScalar(string command){Console.WriteLine("-- Execute command: {0}", command);SqlCommand cmd = new SqlCommand(command, conn1);cmd.Transaction = tx1;return cmd.ExecuteScalar();}

第三步,事務(wù)2修改年齡數(shù)據(jù)并提交修改。

private static void ModifyAgeByTransaction2(){string command = "UPDATE student SET age=30 WHERE id=1";Console.WriteLine("-- Modify age by transaction2, command:{0}", command);SqlCommand cmd = new SqlCommand(command, conn2);cmd.Transaction = tx2;try{cmd.ExecuteNonQuery();tx2.Commit();}catch(Exception e){Console.WriteLine(e.Message);tx2.Rollback();}}

第四步,事務(wù)1重復(fù)讀取年齡數(shù)據(jù),此時會發(fā)現(xiàn)讀取出來的數(shù)據(jù)是修改過的數(shù)據(jù),與上次讀取的數(shù)據(jù)不一樣了!顧名思義,不可重復(fù)讀。主程序代碼如下:

public static void Main(){Setup();try{int age1 = ReadAgeByTransaction1();ModifyAgeByTransaction2();int age2 = ReadAgeByTransaction1();Console.WriteLine("\nFirst Read: age={0}\nSecond Read: age={1}", age1, age2);}catch(Exception e){Console.WriteLine("Got an error! " + e.Message);}finally{CleanUp();}}

程序的運行結(jié)果如下:

-- Execute command: SELECT age FROM student WHERE (id = 1)-- Modify age by transaction2, command:UPDATE student SET age=30 WHERE id=1-- Execute command: SELECT age FROM student WHERE (id = 1)First Read: age=20Second Read: age=30

之所以出現(xiàn)了重復(fù)讀時讀取的數(shù)據(jù)與第一次讀取的不一樣,是因為事務(wù)1被設(shè)置成了ReadCommitted隔離類型,該隔離級別無法防止不可重復(fù)讀的問題。要想在一個事務(wù)中兩次讀取數(shù)據(jù)完全相同就必須使用RepeatableRead事務(wù)隔離級別。


讓我們修改上面的Setup()方法中的代碼,將事務(wù)1的隔離級別設(shè)置為RepeatableRead:

tx1 = conn1.BeginTransaction(IsolationLevel.RepeatableRead);

再次運行該程序,你會發(fā)現(xiàn)程序執(zhí)行到第二步就暫停了,如果等待一段時間后你就會看到"超時時間已到。在操作完成之前超時時間已過或服務(wù)器未響應(yīng)。"的錯誤提示,此時,重復(fù)讀的數(shù)據(jù)確和第一次讀完全一樣。程序執(zhí)行結(jié)果如下:

-- Execute command: SELECT age FROM student WHERE (id = 1)-- Modify age by transaction2, command:UPDATE student SET age=30 WHERE id=1超時時間已到。在操作完成之前超時時間已過或服務(wù)器未響應(yīng)。-- Execute command: SELECT age FROM student WHERE (id = 1)First Read: age=20Second Read: age=20

為了探明原因,還是象上一個案例一樣,再次執(zhí)行該程序,當(dāng)出現(xiàn)暫停時迅速切換到企業(yè)管理器中查看當(dāng)前活動的快照,并檢查阻塞進(jìn)程中數(shù)據(jù)鎖定情況,你會發(fā)現(xiàn)如圖 2-12和圖 2-13所示的內(nèi)容:



 


圖 2-12 RepeatableRead在讀數(shù)據(jù)時加S鎖,直到事務(wù)結(jié)束才釋放


 



圖 2-13 修改數(shù)據(jù)要求加X鎖,但被阻塞


根據(jù)3級封鎖協(xié)議,事務(wù)T在讀取數(shù)據(jù)之前必須先對其加S鎖,直到事務(wù)結(jié)束才釋放。因此,事務(wù)1在第一次讀取數(shù)據(jù)時便對數(shù)據(jù)加上了共享鎖,第一次數(shù)據(jù)讀取完成后事務(wù)并未結(jié)束,因此該共享鎖并不會被釋放,此時事務(wù)2試圖修改該數(shù)據(jù),按照2級封鎖協(xié)議,在寫之前要加排它鎖,但數(shù)據(jù)上的共享鎖尚未被釋放,導(dǎo)致事務(wù)2不得不處于等待狀態(tài)。當(dāng)事務(wù)2等待時間超時后,SQL Server就強(qiáng)制將該事務(wù)回滾。盡管事務(wù)2執(zhí)行失敗,但保證了事務(wù)1實現(xiàn)了可重復(fù)讀級別的事務(wù)隔離。


RepeatableRead事務(wù)隔離級別允許事務(wù)內(nèi)的重復(fù)讀操作,但是這并不能避免出現(xiàn)幻影讀的問題,如果您的程序中存在幻影讀的潛在問題的話,就必須采用最高的事務(wù)隔離級別:Serializable。


2.3.3 Serializable


Serializable隔離級別是最高的事務(wù)隔離級別,在此隔離級別下,不會出現(xiàn)讀臟數(shù)據(jù)、不可重復(fù)讀和幻影讀的問題。在詳細(xì)說明為什么之前首先讓我們看看什么是幻影讀。


所謂幻影讀是指:事務(wù)1按一定條件從數(shù)據(jù)庫中讀取某些數(shù)據(jù)記錄后,事務(wù)2插入了一些符合事務(wù)1檢索條件的新記錄,當(dāng)事務(wù)1再次按相同條件讀取數(shù)據(jù)時,發(fā)現(xiàn)多了一些記錄。讓我們通過以下案例來重現(xiàn)幻影讀的問題:


第一步,將事務(wù)1和事務(wù)2均設(shè)為RepeatableRead隔離級別,并同時開啟事務(wù)。

private static void Setup(){conn1 = new SqlConnection(connectionString);conn1.Open();tx1 = conn1.BeginTransaction(IsolationLevel.RepeatableRead);conn2 = new SqlConnection(connectionString);conn2.Open();tx2 = conn2.BeginTransaction(IsolationLevel.RepeatableRead);}

第二步,事務(wù)1讀取學(xué)號為1的學(xué)生的平均成績以及所學(xué)課程的門數(shù)。此時讀到學(xué)生1學(xué)了3門課程,平均成績?yōu)?3.67。注意,此時事務(wù)1并未提交。

private static double ReadAverageMarksByTransaction1(){return (double)ExecuteScalar("SELECT AVG(mark) AS AvgMark FROM SC WHERE (id = 1)");}private static int ReadTotalCoursesByTransaction1(){return (int)ExecuteScalar("SELECT COUNT(*) AS num FROM SC WHERE (id = 1)");}private static object ExecuteScalar(string command){Console.WriteLine("-- Execute command: {0}", command);SqlCommand cmd = new SqlCommand(command, conn1);cmd.Transaction = tx1;return cmd.ExecuteScalar();}

第三步,事務(wù)2向數(shù)據(jù)庫插入一條新記錄,讓學(xué)號為1的同學(xué)再學(xué)1門課程,成績是80。然后提交修改到數(shù)據(jù)庫。

private static void InsertRecordByTransaction2(){string command = "INSERT INTO SC VALUES(1, 5, 80)";Console.WriteLine("-- Insert to table SC by transaction 2");Console.WriteLine("-- Command:{0}\n", command);SqlCommand cmd = new SqlCommand(command, conn2);cmd.Transaction = tx2;try{cmd.ExecuteNonQuery();tx2.Commit();}catch(Exception e){Console.WriteLine(e.Message);tx2.Rollback();}}

第四步,事務(wù)1再次讀取學(xué)號為1的學(xué)生的平均成績以及所學(xué)課程的門數(shù)。此時讀到確是4門課程,平均成績?yōu)?5.25。與第一次讀取的不一樣!居然多出了一門課程,多出的這門課程就像幻影一樣出現(xiàn)在我們的面前。測試用主程序如下:

public static void Main(){Setup();try{Console.WriteLine(">>>> Step 1");double avg = ReadAverageMarksByTransaction1();int total = ReadTotalCoursesByTransaction1();Console.WriteLine("avg={0,5:F2}, total={1}\n", avg, total);Console.WriteLine(">>>> Step 2");InsertRecordByTransaction2();Console.WriteLine(">>>> Step 3");avg = ReadAverageMarksByTransaction1();total = ReadTotalCoursesByTransaction1();Console.WriteLine("avg={0,5:F2}, total={1}\n", avg, total);}catch(Exception e){Console.WriteLine("Got an error! " + e.Message);}finally{CleanUp();}}

程序執(zhí)行結(jié)果如下:

>>>> Step 1-- Execute command: SELECT AVG(mark) AS AvgMark FROM SC WHERE (id = 1)-- Execute command: SELECT COUNT(*) AS num FROM SC WHERE (id = 1)avg=73.67, total=3>>>> Step 2-- Insert to table SC by transaction 2-- Command:INSERT INTO SC VALUES(1, 5, 80)>>>> Step 3-- Execute command: SELECT AVG(mark) AS AvgMark FROM SC WHERE (id = 1)-- Execute command: SELECT COUNT(*) AS num FROM SC WHERE (id = 1)avg=75.25, total=4

大家可以思考一下,為什么RepeatableRead隔離模式并不能使得兩次讀取的平均值一樣呢?(可以從鎖的角度來解釋這一現(xiàn)象)。


仍然象前面的做法一樣,我們看看究竟發(fā)生了什么事情。在探察之前,先將Setup方法中事務(wù)1的隔離級別設(shè)置為Serializable,再次運行程序,當(dāng)發(fā)現(xiàn)程序運行暫停時,查看數(shù)據(jù)庫當(dāng)前活動快照,你會發(fā)現(xiàn)如圖 2-14和圖 2-15所示的鎖定問題:



 


圖 2-14 Serializable隔離模式對符合檢索條件的數(shù)據(jù)添加了RangeS-S鎖



 


圖 2-15 當(dāng)試圖插入符合RangeIn條件的記錄時,只能處于等待狀態(tài)


從圖中我們可以看出,在Serializalbe隔離模式下,數(shù)據(jù)庫在檢索數(shù)據(jù)時,對所有滿足檢索條件的記錄均加上了RangeS-S共享鎖。事務(wù)2試圖去插入一滿足RangeIn條件的記錄時,必須等待這些RangS-S鎖釋放,否則就只能處于等待狀態(tài)。在等待超時后,事務(wù)2就會被SQL Server強(qiáng)制回滾。


修改后的程序運行結(jié)果如下:

>>>> Step 1-- Execute command: SELECT AVG(mark) AS AvgMark FROM SC WHERE (id = 1)-- Execute command: SELECT COUNT(*) AS num FROM SC WHERE (id = 1)avg=73.67, total=3>>>> Step 2-- Insert to table SC by transaction 2-- Command:INSERT INTO SC VALUES(1, 5, 80)超時時間已到。在操作完成之前超時時間已過或服務(wù)器未響應(yīng)。>>>> Step 3-- Execute command: SELECT AVG(mark) AS AvgMark FROM SC WHERE (id = 1)-- Execute command: SELECT COUNT(*) AS num FROM SC WHERE (id = 1)avg=73.67, total=3

事務(wù)2的運行失敗確保了事務(wù)1不會出現(xiàn)幻影讀的問題。這里應(yīng)當(dāng)注意的是,1、2、3級封鎖協(xié)議都不能保證有效解決幻影讀的問題。


2.3 建議


通過上面的幾個例子,我們更深入的了解了數(shù)據(jù)庫在解決并發(fā)一致性問題時所采取的措施。鎖機(jī)制屬于最底層的保證機(jī)制,但很難直接使用。我們可以通過不同的事務(wù)隔離模式來間接利用鎖定機(jī)制確保我們數(shù)據(jù)的完整一致性。在使用不同級別的隔離模式時,我們也應(yīng)當(dāng)注意以下一些問題:



  • 一般情況下ReadCommitted隔離級別就足夠了。過高的隔離級別將會鎖定過多的資源,影響數(shù)據(jù)的共享效率。
  • 你所選擇的隔離級別依賴于你的系統(tǒng)和商務(wù)邏輯。
  • 盡量避免直接使用鎖,除非在萬不得已的情況下。
  • 我們可以通過控制WHERE短語中的字段實現(xiàn)不同的更新策略,防止出現(xiàn)丟失的修改問題。但不必要的更新策略可能造成SQL命令執(zhí)行效率低下。所以要慎用時間戳和過多的保護(hù)字段作為更新依據(jù)。 

該文章在 2011/3/3 16:32:47 編輯過
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點晴ERP是一款針對中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國內(nèi)大量中小企業(yè)的青睞。
點晴PMS碼頭管理系統(tǒng)主要針對港口碼頭集裝箱與散貨日常運作、調(diào)度、堆場、車隊、財務(wù)費用、相關(guān)報表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點,圍繞調(diào)度、堆場作業(yè)而開發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點晴WMS倉儲管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購管理,倉儲管理,倉庫管理,保質(zhì)期管理,貨位管理,庫位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號管理軟件。
點晴免費OA是一款軟件和通用服務(wù)都免費,不限功能、不限時間、不限用戶的免費OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved