SQL Server 2000+ADO.NET實現(xiàn)并發(fā)控制
當前位置:點晴教程→知識管理交流
→『 技術文檔交流 』
1 并發(fā)一致性問題
常見并發(fā)并發(fā)一致性問題包括:丟失的修改、不可重復讀、讀臟數(shù)據(jù)、幻影讀(幻影讀在一些資料中往往與不可重復讀歸為一類)。 1.1 丟失修改下面我們先來看一個例子,說明并發(fā)操作帶來的數(shù)據(jù)的不一致性問題。 考慮飛機訂票系統(tǒng)中的一個活動序列:
結果明明賣出兩張機票,數(shù)據(jù)庫中機票余額只減少1。 歸納起來就是:兩個事務T1和T2讀入同一數(shù)據(jù)并修改,T2提交的結果破壞了T1提交的結果,導致T1的修改被丟失。前文(2.1.4數(shù)據(jù)刪除與更新)中提到的問題及解決辦法往往是針對此類并發(fā)問題的。但仍然有幾類問題通過上面的方法解決不了,那就是: 1.2 不可重復讀不可重復讀是指事務T1讀取數(shù)據(jù)后,事務T2執(zhí)行更新操作,使T1無法再現(xiàn)前一次讀取結果。具體地講,不可重復讀包括三種情況:
1.3 讀"臟"數(shù)據(jù)讀"臟"數(shù)據(jù)是指事務T1修改某一數(shù)據(jù),并將其寫回磁盤,事務T2讀取同一數(shù)據(jù)后,T1由于某種原因被撤消,這時T1已修改過的數(shù)據(jù)恢復原值,T2讀到的數(shù)據(jù)就與數(shù)據(jù)庫中的數(shù)據(jù)不一致,則T2讀到的數(shù)據(jù)就為"臟"數(shù)據(jù),即不正確的數(shù)據(jù)。 產生上述三類數(shù)據(jù)不一致性的主要原因是并發(fā)操作破壞了事務的隔離性。并發(fā)控制就是要用正確的方式調度并發(fā)操作,使一個用戶事務的執(zhí)行不受其它事務的干擾,從而避免造成數(shù)據(jù)的不一致性。 2 并發(fā)一致性問題的解決辦法2.2.2.1 封鎖(Locking)封鎖是實現(xiàn)并發(fā)控制的一個非常重要的技術。所謂封鎖就是事務T在對某個數(shù)據(jù)對象例如表、記錄等操作之前,先向系統(tǒng)發(fā)出請求,對其加鎖。加鎖后事務T就對該數(shù)據(jù)對象有了一定的控制,在事務T釋放它的鎖之前,其它的事務不能更新此數(shù)據(jù)對象。 基本的封鎖類型有兩種:排它鎖(Exclusive locks 簡記為X鎖)和共享鎖(Share locks 簡記為S鎖)。 排它鎖又稱為寫鎖。若事務T對數(shù)據(jù)對象A加上X鎖,則只允許T讀取和修改A,其它任何事務都不能再對A加任何類型的鎖,直到T釋放A上的鎖。這就保證了其它事務在T釋放A上的鎖之前不能再讀取和修改A。 共享鎖又稱為讀鎖。若事務T對數(shù)據(jù)對象A加上S鎖,則其它事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這就保證了其它事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。 2.2.2.2 封鎖協(xié)議在運用X鎖和S鎖這兩種基本封鎖,對數(shù)據(jù)對象加鎖時,還需要約定一些規(guī)則,例如應何時申請X鎖或S鎖、持鎖時間、何時釋放等。我們稱這些規(guī)則為封鎖協(xié)議(Locking Protocol)。對封鎖方式規(guī)定不同的規(guī)則,就形成了各種不同的封鎖協(xié)議。下面介紹三級封鎖協(xié)議。三級封鎖協(xié)議分別在不同程度上解決了丟失的修改、不可重復讀和讀"臟"數(shù)據(jù)等不一致性問題,為并發(fā)操作的正確調度提供一定的保證。下面只給出三級封鎖協(xié)議的定義,不再做過多探討。
1級封鎖協(xié)議是:事務T在修改數(shù)據(jù)R之前必須先對其加X鎖,直到事務結束才釋放。事務結束包括正常結束(COMMIT)和非正常結束(ROLLBACK)。1級封鎖協(xié)議可防止丟失修改,并保證事務T是可恢復的。在1級封鎖協(xié)議中,如果僅僅是讀數(shù)據(jù)不對其進行修改,是不需要加鎖的,所以它不能保證可重復讀和不讀"臟"數(shù)據(jù)。
2級封鎖協(xié)議是:1級封鎖協(xié)議加上事務T在讀取數(shù)據(jù)R之前必須先對其加S鎖,讀完后即可釋放S鎖。2級封鎖協(xié)議除防止了丟失修改,還可進一步防止讀"臟"數(shù)據(jù)。
3級封鎖協(xié)議是:1級封鎖協(xié)議加上事務T在讀取數(shù)據(jù)R之前必須先對其加S鎖,直到事務結束才釋放。3級封鎖協(xié)議除防止了丟失修改和不讀'臟'數(shù)據(jù)外,還進一步防止了不可重復讀。 2.3 事務隔離級別盡管數(shù)據(jù)庫理論對并發(fā)一致性問題提供了完善的解決機制,但讓程序員自己去控制如何加鎖以及加鎖、解鎖的時機顯然是很困難的事情。索性絕大多數(shù)數(shù)據(jù)庫以及開發(fā)工具都提供了事務隔離級別,讓用戶以一種更輕松的方式處理并發(fā)一致性問題。常見的事務隔離級別包括:ReadUnCommitted、ReadCommitted、RepeatableRead和Serializable四種。不同的隔離級別下對數(shù)據(jù)庫的訪問方式以及數(shù)據(jù)庫的返回結果有可能是不同的。我們將通過幾個實驗深入了解事務隔離級別以及SQL Server在后臺是如何將它們轉換成鎖的。 2.3.1 ReadUnCommitted與ReadCommittedReadUnCommitted是最低的隔離級別,這個級別的隔離允許讀入別人尚未提交的臟數(shù)據(jù),除此之外,在這種事務隔離級別下還存在不可重復讀的問題。 ReadCommitted是許多數(shù)據(jù)庫的缺省級別,這個隔離級別上,不會出現(xiàn)讀取未提交的數(shù)據(jù)問題,但仍然無法避免不可重復讀(包括幻影讀)的問題。當你的系統(tǒng)對并發(fā)控制的要求非常嚴格時,這種默認的隔離級別可能無法提供數(shù)據(jù)有效的保護,但對于決大多數(shù)應用來講,這種隔離級別就夠用了。 我們使用下面的實驗來進行測試: 首先配置SQL Server 2000數(shù)據(jù)庫,附加DBApp數(shù)據(jù)庫。然后在Visual Studio .net中建立一管理控制臺應用程序,添加必要的命名空間引用: using System;using System.Data;using System.Data.SqlClient;using System.Configuration; 然后建立兩個數(shù)據(jù)庫鏈接,并分別采用不同的事務隔離級別: 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);} 其中事務1允許讀入未提交的數(shù)據(jù),而事務2只允許讀入已提交數(shù)據(jù)。 在主程序中,我們模擬兩個人先后的不同操作,以產生并發(fā)一致性問題: public static void Main(){Setup();try{ReadUnCommittedDataByTransaction1();UnCommittedUpdateByTransaction2();ReadUnCommittedDataByTransaction1();tx2.Rollback();Console.WriteLine("\n-- Transaction 2 rollbacked!\n");ReadUnCommittedDataByTransaction1();tx1.Rollback();}catch{……}} 第一步,使用ReadUnCommittedDataByTransaction1方法利用事務1從數(shù)據(jù)庫中讀入id值為1的學生信息。此時的信息是數(shù)據(jù)庫的初始信息。 第二步,調用UnCommittedUpdateByTransaction2方法,從第2個事務中發(fā)送一UPDATE命令更新數(shù)據(jù)庫,但尚未提交。 第三步,再次調用ReadUnCommittedDataByTransaction1,從事務1中讀取數(shù)據(jù)庫數(shù)據(jù),你會發(fā)現(xiàn)由事務2發(fā)布的尚未提交的更新被事務1讀取出來(ReadUnCommitted)。 第四步,事務2放棄提交,回滾事務tx2.Rollback();。 第五步,再次調用ReadUnCommittedDataByTransaction1();,讀取數(shù)據(jù)庫中的數(shù)據(jù),此次是已經回滾后的數(shù)據(jù)。 程序運行結果如下: -- 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 關于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"下找到。 讓我們再來做一個實驗(這個實驗要求動作要快的,否則可能看不到預期效果)。首先修改上面代碼中的Setup()方法代碼,將
改為:
再次運行代碼,你會發(fā)現(xiàn)程序執(zhí)行到第三步就不動了,如果你有足夠的耐心等下去的話,你會看到"超時時間已到。在操作完成之前超時時間已過或服務器未響應。"的一條提示,這條提示究竟是什么意思呢?讓我們探察一下究竟發(fā)生了什么: 第一步,在做這個實驗之前,先將SQL Server 2000的企業(yè)管理器打開,然后再將SQL Server事件探察器打開并處于探察狀態(tài)。 第二步,運行改動后的程序,程序執(zhí)行到一半就暫停了。此時迅速切換到企業(yè)管理器界面,右擊"管理"下面的"當前活動",選擇"刷新"(整個過程應在大約15秒內完成即可,如圖 2-8所示),我們便得到了數(shù)據(jù)庫當前進程的一個快照。
圖 2-8 使用企業(yè)管理器查看當前活動 我們發(fā)現(xiàn)此時進程出現(xiàn)了阻塞,被阻塞者是52號進程,而阻塞者是53號進程。也就是說53號進程的工作妨礙了52號進程繼續(xù)工作。(不同實驗時進程號可能各不相同) 第三步,為了進一步查明原因真相,我們切換到事件探察器窗口,看看這兩個進程都是干什么的。如圖 2-9所示,事件探察器顯示了這兩個進程的詳細信息。從圖中我們可以看出,52號進程對應我們的事務1,53號進程對應我們的事務2。事務2執(zhí)行了UPDATE命令,但尚未提交,此時事務1去讀尚未提交的數(shù)據(jù)便被阻塞住。從圖中我們可以看出52號進程是被阻塞者。 此時如果事務2完成提交,52號進程便可以停止等待,得到需要的結果。然而我們的程序沒有提交數(shù)據(jù),因此52號進程就要無限等下去。所幸SQL Server 2000檢測到事務2的運行時間過長(這就是上面的錯誤提示"超時時間已到。在操作完成之前超時時間已過或服務器未響應。"),所以將事務2回滾以釋放占用的資源。資源被釋放后,52號進程便得以執(zhí)行。
圖 2-9 事件探察器探察阻塞命令 第四步,了解了上面發(fā)生的事情后,我們現(xiàn)在可以深入討論一下共享鎖和排它鎖的使用情況了。重新回到企業(yè)管理器界面,讓我們查看一下兩個進程各占用了什么資源。從圖 2-10中我們可以看出,53號進程(事務2)在執(zhí)行更新命令前對相應的鍵加上了排它鎖(X鎖),按照前文提到的1級封鎖協(xié)議,該排它鎖只有在事務2提交或回滾后才釋放。現(xiàn)在52號進程(事務1)要去讀同一行數(shù)據(jù),按照2級封鎖協(xié)議,它要首先對該行加共享鎖,然而 該行數(shù)據(jù)已經被事務2加上了排它鎖,因此事務1只能處于等待狀態(tài),等待排它鎖被釋放。因此我們就看到了前面的"阻塞"問題。
圖 2-10 進程執(zhí)行寫操作前首先加了排它鎖
圖 2-11 進程讀操作前要加共享鎖,但被阻塞
當事務1的事務隔離級別是ReadUnCommitted時,讀數(shù)據(jù)是不加鎖的,因此排它鎖對ReadUnCommitted不起作用,進程也不會被阻塞,不過確讀到了"臟"數(shù)據(jù)。 2.3.2 RepeatableReadRepeatableRead是指可重復讀,它的隔離級別要比ReadCommitted級別高。它允許某事務執(zhí)行重復讀時數(shù)據(jù)保持不變,但是仍然無法解決幻影讀的問題。為了更深入的了解RepeatableRead所能解決的問題,我們還是使用下面的實驗來加以印證: 第一步,事務1與事務2同時設置為ReadCommitted,并同時開啟事務。
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);} 第二步,事務1讀取數(shù)據(jù)庫中數(shù)據(jù)。注意此時并沒有通過提交或回滾的方式結束事務1,事務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();} 第三步,事務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();}} 第四步,事務1重復讀取年齡數(shù)據(jù),此時會發(fā)現(xiàn)讀取出來的數(shù)據(jù)是修改過的數(shù)據(jù),與上次讀取的數(shù)據(jù)不一樣了!顧名思義,不可重復讀。主程序代碼如下: 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();}} 程序的運行結果如下: -- 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)了重復讀時讀取的數(shù)據(jù)與第一次讀取的不一樣,是因為事務1被設置成了ReadCommitted隔離類型,該隔離級別無法防止不可重復讀的問題。要想在一個事務中兩次讀取數(shù)據(jù)完全相同就必須使用RepeatableRead事務隔離級別。 讓我們修改上面的Setup()方法中的代碼,將事務1的隔離級別設置為RepeatableRead: tx1 = conn1.BeginTransaction(IsolationLevel.RepeatableRead); 再次運行該程序,你會發(fā)現(xiàn)程序執(zhí)行到第二步就暫停了,如果等待一段時間后你就會看到"超時時間已到。在操作完成之前超時時間已過或服務器未響應。"的錯誤提示,此時,重復讀的數(shù)據(jù)確和第一次讀完全一樣。程序執(zhí)行結果如下: -- 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=20 為了探明原因,還是象上一個案例一樣,再次執(zhí)行該程序,當出現(xiàn)暫停時迅速切換到企業(yè)管理器中查看當前活動的快照,并檢查阻塞進程中數(shù)據(jù)鎖定情況,你會發(fā)現(xiàn)如圖 2-12和圖 2-13所示的內容:
圖 2-12 RepeatableRead在讀數(shù)據(jù)時加S鎖,直到事務結束才釋放
圖 2-13 修改數(shù)據(jù)要求加X鎖,但被阻塞 根據(jù)3級封鎖協(xié)議,事務T在讀取數(shù)據(jù)之前必須先對其加S鎖,直到事務結束才釋放。因此,事務1在第一次讀取數(shù)據(jù)時便對數(shù)據(jù)加上了共享鎖,第一次數(shù)據(jù)讀取完成后事務并未結束,因此該共享鎖并不會被釋放,此時事務2試圖修改該數(shù)據(jù),按照2級封鎖協(xié)議,在寫之前要加排它鎖,但數(shù)據(jù)上的共享鎖尚未被釋放,導致事務2不得不處于等待狀態(tài)。當事務2等待時間超時后,SQL Server就強制將該事務回滾。盡管事務2執(zhí)行失敗,但保證了事務1實現(xiàn)了可重復讀級別的事務隔離。 RepeatableRead事務隔離級別允許事務內的重復讀操作,但是這并不能避免出現(xiàn)幻影讀的問題,如果您的程序中存在幻影讀的潛在問題的話,就必須采用最高的事務隔離級別:Serializable。 2.3.3 SerializableSerializable隔離級別是最高的事務隔離級別,在此隔離級別下,不會出現(xiàn)讀臟數(shù)據(jù)、不可重復讀和幻影讀的問題。在詳細說明為什么之前首先讓我們看看什么是幻影讀。 所謂幻影讀是指:事務1按一定條件從數(shù)據(jù)庫中讀取某些數(shù)據(jù)記錄后,事務2插入了一些符合事務1檢索條件的新記錄,當事務1再次按相同條件讀取數(shù)據(jù)時,發(fā)現(xiàn)多了一些記錄。讓我們通過以下案例來重現(xiàn)幻影讀的問題: 第一步,將事務1和事務2均設為RepeatableRead隔離級別,并同時開啟事務。 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);} 第二步,事務1讀取學號為1的學生的平均成績以及所學課程的門數(shù)。此時讀到學生1學了3門課程,平均成績?yōu)?3.67。注意,此時事務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();} 第三步,事務2向數(shù)據(jù)庫插入一條新記錄,讓學號為1的同學再學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();}} 第四步,事務1再次讀取學號為1的學生的平均成績以及所學課程的門數(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í)行結果如下: >>>> 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方法中事務1的隔離級別設置為Serializable,再次運行程序,當發(fā)現(xiàn)程序運行暫停時,查看數(shù)據(jù)庫當前活動快照,你會發(fā)現(xiàn)如圖 2-14和圖 2-15所示的鎖定問題:
圖 2-14 Serializable隔離模式對符合檢索條件的數(shù)據(jù)添加了RangeS-S鎖
圖 2-15 當試圖插入符合RangeIn條件的記錄時,只能處于等待狀態(tài) 從圖中我們可以看出,在Serializalbe隔離模式下,數(shù)據(jù)庫在檢索數(shù)據(jù)時,對所有滿足檢索條件的記錄均加上了RangeS-S共享鎖。事務2試圖去插入一滿足RangeIn條件的記錄時,必須等待這些RangS-S鎖釋放,否則就只能處于等待狀態(tài)。在等待超時后,事務2就會被SQL Server強制回滾。 修改后的程序運行結果如下: >>>> 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=73.67, total=3 事務2的運行失敗確保了事務1不會出現(xiàn)幻影讀的問題。這里應當注意的是,1、2、3級封鎖協(xié)議都不能保證有效解決幻影讀的問題。 2.3 建議通過上面的幾個例子,我們更深入的了解了數(shù)據(jù)庫在解決并發(fā)一致性問題時所采取的措施。鎖機制屬于最底層的保證機制,但很難直接使用。我們可以通過不同的事務隔離模式來間接利用鎖定機制確保我們數(shù)據(jù)的完整一致性。在使用不同級別的隔離模式時,我們也應當注意以下一些問題:
該文章在 2011/3/3 16:32:47 編輯過 |
關鍵字查詢
相關文章
正在查詢... |