|
事務(wù)(Transaction)是并發(fā)控制的基本單位。所謂事務(wù),它是一個(gè)操作序列,這些操作要么都執行,要么都不執行,它是一個(gè)不可分割的工作單位。例如,銀行轉帳工作:從一個(gè)賬號扣款并使另一個(gè)賬號增款,這兩個(gè)操作要么都執行,要么都不執行。所以,應該把他們看成一個(gè)事務(wù)。事務(wù)是數據庫維護數據一致性的單位,在每個(gè)事務(wù)結束時(shí),都能保持數據一致性。
數據一致性問(wèn)題 多用戶(hù)并發(fā)存取同一數據將會(huì )導致以下的數據不一致性問(wèn)題。 • 丟失修改( Lost Update) 在下表中,T1、T2、T3和T4表示順序的時(shí)間。 用戶(hù) T 1 T 2 T 3 T 4 A x = 40 X = x-30 B X = 40 X = x-20 假設用戶(hù)A和B都讀取x ( x = 40 ) ,然后分別把x減少30和20。用戶(hù)A在t3把改后的x ( x = 10 )寫(xiě)入數據庫。隨后,用戶(hù)B在t4把改后的x ( x = 20 )寫(xiě)入數據庫。于是,對用戶(hù)A而言,他的修改在t4 處丟失了。 • 臟讀數據( Dirty Read) 請看下表, 用戶(hù) T1 T2 T3 T4 A x = 40 X = x + 30 X = x - 30 rollback B X = 70 X = x-20 用戶(hù)A在t2把x增加30(尚沒(méi)寫(xiě)入數據庫),用戶(hù)B在t3由數據緩存讀出x = 70。但用戶(hù)A在t4時(shí)撤消(Undo)了對x的修改,數據庫中仍維持x = 40。但用戶(hù)B已把改變的數據( x = 70)取走。 • 不能重復讀(Non-Repeatable Read) 用戶(hù) T1 T2 T3 T4 T5 T6 A X=40 Y=30 X+Y=70 Z=30 X+Y+Z=100 B x=40 X=X+20 Commit X=x-20 用戶(hù)A、用戶(hù)B分別讀取x = 40后,在t 3用戶(hù)A取出y = 30并計算x + y = 70。在t4時(shí)用戶(hù)B把x增加20,并于t 5把x ( x = 60 )寫(xiě)入數據庫。在t6時(shí),用戶(hù)A取出z ( z = 30 )并繼續計算x + y + z = 100。但如果用戶(hù)A為進(jìn)行核算而把x、y、x重讀一次再進(jìn)行計算,卻出現x + y + z = 120?。▁已增加20)。 如何標識一個(gè)事務(wù) 在SQL Server中,通常事務(wù)是指以BEGIN TRAN開(kāi)始,到ROLLBACK或一個(gè)相匹配的COMMIT之間的所有語(yǔ)句序列。ROLLBACK表示要撤消( U n d o)該事務(wù)已做的一切操作,回退到事務(wù)開(kāi)始的狀態(tài)。COMMIT表示提交事務(wù)中的一切操作,使得對數據庫的改變生效。 在SQL Server中,對事務(wù)的管理包含三個(gè)方面: • 事務(wù)控制語(yǔ)句:它使程序員能指明把一系列操作( Transact - SQL命令)作為一個(gè)工作單 位來(lái)處理。 • 鎖機制( Locking):封鎖正被一個(gè)事務(wù)修改的數據,防止其他用戶(hù)訪(fǎng)問(wèn)到“不一致”的數據。 • 事務(wù)日志( Transaction Log):使事務(wù)具有可恢復性。 SQL Server的鎖機制 所謂封鎖,就是一個(gè)事務(wù)可向系統提出請求,對被操作的數據加鎖( Lock )。其他事務(wù)必須等到此事務(wù)解鎖( Unlock)之后才能訪(fǎng)問(wèn)該數據。從而,在多個(gè)用戶(hù)并發(fā)訪(fǎng)問(wèn)數據庫時(shí),確保不互相干擾??涉i定的單位是:行、頁(yè)、表、盤(pán)區和數據庫。 1. 鎖的類(lèi)型 SQL Server支持三種基本的封鎖類(lèi)型:共享( S)鎖,排它(X)鎖和更新(U)鎖。封鎖的基本粒度為行。 1) 共享(S)鎖:用于讀操作。 • 多個(gè)事務(wù)可封鎖一個(gè)共享單位的數據。 • 任何事務(wù)都不能修改加S鎖的數據。 • 通常是加S鎖的數據被讀取完畢,S鎖立即被釋放。 2) 獨占(X)鎖:用于寫(xiě)操作。 • 僅允許一個(gè)事務(wù)封鎖此共享數據。 • 其他任何事務(wù)必須等到X鎖被釋放才能對該數據進(jìn)行訪(fǎng)問(wèn)。 • X鎖一直到事務(wù)結束才能被釋放。 3) 更新(U)鎖。 • 用來(lái)預定要對此頁(yè)施加X鎖,它允許其他事務(wù)讀,但不允許再施加U鎖或X鎖。 • 當被讀取數據頁(yè)將要被更新時(shí),則升級為X鎖。 • U鎖一直到事務(wù)結束時(shí)才能被釋放。 2. 三種鎖的相容性 如下表簡(jiǎn)單描述了三種鎖的相容性: 通常,讀操作(SELECT)獲得共享鎖,寫(xiě)操作( INSERT、DELETE)獲得獨占鎖;而更新操作可分解為一個(gè)有更新意圖的讀和一個(gè)寫(xiě)操作,故先獲得更新鎖,然后再升級為獨占鎖。 執行的命令 獲得鎖 其他進(jìn)程可以查詢(xún)? 其他進(jìn)程可以修改? Select title_id from titles S Yes No delete titles where price>25 X No No insert titles values( ...) X No No update titles set type=“general” U Yes No where type=“business” 然后X NO No 使用索引降低鎖并發(fā)性 我們?yōu)槭裁匆懻撴i機制?如果用戶(hù)操作數據時(shí)盡可能鎖定最少的數據,這樣處理過(guò)程,就不會(huì )等待被鎖住的數據解鎖,從而可以潛在地提高SQL Server的性能。如果有200個(gè)用戶(hù)打算修改不同顧客的數據,僅對存儲單個(gè)顧客信息的單一行進(jìn)行加鎖要比鎖住整個(gè)表好得多。那么,用戶(hù)如何只鎖定行而不是表呢?當然是使用索引了。正如前面所提到的,對存有要修改數據的字段使用索引可以提高性能,因為索引能直接找到數據所在的頁(yè)面,而不是搜索所有的數據頁(yè)面去找到所需的行。如果用戶(hù)直接找到表中對應的行并進(jìn)行更新操作,只需鎖定該行即可,而不是鎖定多個(gè)頁(yè)面或者整個(gè)表。性能的提高不僅僅是因為在修改時(shí)讀取的頁(yè)面較少,而且鎖定較少的頁(yè)面潛在地避免了一個(gè)用戶(hù)在修改數據完成之前其他用戶(hù)一直等待解鎖的情況。 事務(wù)的隔離級別 ANSI標準為SQL事務(wù)定義了4個(gè)隔離級別(isolation level),隔離級別越高,出現數據不一致性的可能性就越小(并發(fā)度也就越低)。較高的級別中包含了較低級別中所規定了的限制。 • 隔離級別0:防止“丟失修改”,允許臟讀。 • 隔離級別1:防止臟讀。允許讀已提交的數據。 • 隔離級別2:防止“不可重復讀”。 • 隔離級別3:“可串行化”(serializable)。其含義為,某組并行事務(wù)的一種交叉調度產(chǎn)生的結果和這些事務(wù)的某一串行調度的結果相同(可避免破壞數據一致性)。SQL Server支持四種隔離級別,級別1為缺省隔離級別,表中沒(méi)有隔離級別2, 請參考表: SQL Server支持的隔離級別 封鎖方式 數據一致性保證 X鎖施加于被修改的頁(yè) S鎖施加于被讀取的頁(yè) 防止丟失修改 防止讀臟數據 可以重復讀取 級別0 封鎖到事務(wù)結束 是 級別1(缺?。?nbsp; 封鎖到事務(wù)結束 讀后立即釋放 是 是 級別3 封鎖到事務(wù)結束 封鎖到事務(wù)結束 是 是 是 在SQL Server也指定級別2,但級別3已包含級別2。ANSI-92 SQL中要求把級別3作為所有事務(wù)的缺省隔離級別。 SQL Server用holdlock選項加強S鎖的限制,實(shí)現隔離級別3。SQL Server的缺省隔離級別為級別1,共享讀鎖(S鎖)是在該頁(yè)被讀完后立即釋放。在select語(yǔ)句中加holdlock選項,則可使S鎖一直保持到事務(wù)結束才釋放。她符合了ANSI隔離級別3的標準─“可串行化”。 下面這個(gè)例子中,在同一事務(wù)中對avg ( advance )要讀取兩次,且要求他們取值不變─“可重復讀”,為此要使用選項holdlock。 BEGIN tran DECLARE @avg-adv money SELECT @avg-adv = avg(advance) FROM titles holdlock WHERE type = "business" if @avg-adv > 5000 SELECT title from titles WHERE type="business" and advance >@avg_adv COMMIT tran 在SQL Server中設定事務(wù)隔離級別的方法有三種: • 會(huì )話(huà)層設定 語(yǔ)法如下: SET TRANSACTION ISOLATION LEVEL { READ COMMITTED | READ UNCOMMITTED | REPEATABLE READ | SERIALIZABLE } 系統提供的系統存儲過(guò)程將在級別1下執行,它不受會(huì )話(huà)層設定的影響。 • 語(yǔ)法層設定 在SELECT、DECLARE cursor及read text語(yǔ)句中增加選項。比如: SELECT...at isolation{0|read uncommitted} 注意:語(yǔ)法層的設定將替代會(huì )話(huà)層的設定。 • 利用關(guān)鍵詞設定 ─在SELECT語(yǔ)句中,加選項holdlock則設定級別3 ─在SELECT語(yǔ)句中,加noholdlock則設定級別0 如下程序清單中所列的腳本實(shí)例在authors表上持有一個(gè)共享鎖,它將用戶(hù)檢查服務(wù)器當前活動(dòng)的時(shí)間推遲兩分鐘。 程序清單測試事務(wù)隔離等級 SET TRANSACTION ISOLATION LEVEL REPEATABLE READ GO BEGIN TRAN SELECT * FROM authors WHERE au_lname = 'Green' WAITFOR DELAY '00:02:00' ROLLBACK TRAN GO Activity Legend(活動(dòng)圖標)表明:當SQL Server檢索數據時(shí)會(huì )去掉頁(yè)面表意向鎖。Current Activity窗口(見(jiàn)圖3 - 3 )顯示共享鎖一直被保持直到事務(wù)完成為止(也就是說(shuō),直到WAITFOR和ROLLBACK TRAN語(yǔ)句完成)。 使用鎖定優(yōu)化程序提示 讓我們再深入考察程序清單的實(shí)例。通過(guò)改變優(yōu)化程序提示,用戶(hù)可以令SQL Server在authors表上設置一個(gè)獨占表鎖(如程序所示)。 BEGIN TRAN SELECT * FROM authors (tablockx) WHERE au_lname = 'Green' WAITFOR DELAY '00:02:00' ROLLBACK TRAN GO SELECT語(yǔ)句使用優(yōu)化程序提示tablockx來(lái)保持獨占表鎖直到事務(wù)結束為止。下表顯示了可用的鎖定優(yōu)化程序提示。 鎖定優(yōu)化程序提示及其描述 優(yōu)化程序提示 優(yōu)化程序提示描述 holdlock 保持鎖定直到事務(wù)結束 nolock 檢索數據時(shí)不使用鎖 paglock 使用頁(yè)面鎖 tablock 使用表鎖 tablockx 使用獨占表鎖 updlock 使用更新鎖 holdlock優(yōu)化程序提示能夠在整個(gè)事務(wù)期間保持共享鎖,讀者在可串行化和可重復讀事務(wù)隔離等級中對此已很熟悉了。如果用戶(hù)偶爾想使用共享鎖,最好使用系統默認的讀交付事務(wù)隔離等級并需要使用holdlock優(yōu)化程序提示。holock優(yōu)化程序提示與讀不交付事務(wù)隔離等級有相同的功能,它通過(guò)在讀數據時(shí)不要任何鎖定而實(shí)現非交付數據的讀操作(從而避免了任何獨占鎖定引起的阻隔)。使用索引和鎖定優(yōu)化程序提示需要注意的是:用戶(hù)可以將這兩種類(lèi)型的提示結合起來(lái)使 用,但必須將索引提示最后列出,這一點(diǎn)很重要。如下程序清單中的代碼給出了合法優(yōu)化程序提示的正確方法。如一個(gè)混合優(yōu)化程序提示 SELECT * FROM authors (paglock holdlock index=aunmind) Delphi的Dataset屬性已封裝好以上的解決方法﹐ 當讀數據時(shí): CacheSize property = 1000 CursorLocation = clUseClient LockType = ltReadOnly CursorType = ctKeyset CommandTimeOut = 30 當打印數據時(shí)﹕ CacheSize property = 1000 CursorLocation = clUseClient LockType = ltReadOnly CursorType = ctOpenForwardOnly CommandTimeOut = 30 當寫(xiě)數據時(shí): CacheSize property = 1000 CursorLocation = clUseServer LockType = ltPessimistic CursorType = ctKeyset CommandTimeOut = 5 |
聯(lián)系客服