前言
事務(wù)一直以來(lái)是我最薄弱的環(huán)節(jié),也是我打算重新學(xué)習(xí)SQL Server的出發(fā)點(diǎn),關(guān)于SQL Server中事務(wù)將分為幾節(jié)來(lái)進(jìn)行闡述,Always to review the basics。
事務(wù)簡(jiǎn)介
事務(wù)是一個(gè)工作單元,可能包含查詢和修改數(shù)據(jù)以及修改數(shù)據(jù)定義等多個(gè)活動(dòng)。我們可以顯式或隱式的定義事務(wù)邊界??梢允褂肂EGIN TRAN或者BEGIN TRANSACTION語(yǔ)句顯式的定義事務(wù)的開(kāi)始。如果希望提交事務(wù),可以使用COMMIT TRAN語(yǔ)句顯式的定義事務(wù)結(jié)束。如果不希望提交事務(wù)(即要撤銷(xiāo)更改),可以使用ROLLBACK TRAN或者ROLLBACK TRANSACTION語(yǔ)句-摘抄自SQL Server 2012基礎(chǔ)教程。
如果不顯式的標(biāo)記事務(wù)的邊界,默認(rèn)情況下,SQL Server將把每個(gè)單獨(dú)語(yǔ)句作為一個(gè)事務(wù),換句話說(shuō),默認(rèn)情況下,每個(gè)單獨(dú)語(yǔ)句結(jié)束后SQL Server自動(dòng)提交事務(wù)??梢酝ㄟ^(guò)一個(gè)叫做IMPLICIT_TRANSACTIONS的回話選項(xiàng)修改SQL Server處理隱式事務(wù)的方式,此選項(xiàng)默認(rèn)為OFF。當(dāng)此選項(xiàng)為ON時(shí),不需要指定BEGIN TRAN語(yǔ)句標(biāo)記事務(wù)的開(kāi)始,但是必須以COMMIT TRAN或者ROLLBACK TRAN語(yǔ)句標(biāo)記事務(wù)的結(jié)束-摘抄自SQL Server 2012基礎(chǔ)教程。
事務(wù)具有原子性、一致性、隔離性、持續(xù)性四個(gè)屬性,縮寫(xiě)字母為ACID。
(1)原子性:事務(wù)是一個(gè)工作單元,事務(wù)中的所有修改要么提交、要么撤銷(xiāo),在事務(wù)完成之前如果系統(tǒng)出現(xiàn)故障,重新啟動(dòng)時(shí)SQL Server會(huì)撤銷(xiāo)所做的修改。
(2)一致性:一致性是指數(shù)據(jù)的狀態(tài),RDMS提供了以并發(fā)事務(wù)修改和查詢數(shù)據(jù)的能力。
(3)隔離性:隔離是用于控制訪問(wèn)數(shù)據(jù)的機(jī)制,確保事務(wù)所訪問(wèn)數(shù)據(jù)是在其期望的一致性級(jí)別中的數(shù)據(jù),SQL Server支持兩種不同的模式來(lái)處理隔離:基于鎖的傳統(tǒng)模式和基于行版本控制的新模式,在企業(yè)內(nèi)部部署的SQL Server中,默認(rèn)是基于鎖的模式。
(4)持續(xù)性:數(shù)據(jù)修改寫(xiě)入到數(shù)據(jù)庫(kù)磁盤(pán)上的數(shù)據(jù)部分之前,總是先寫(xiě)入到數(shù)據(jù)庫(kù)的事務(wù)日志磁盤(pán),在提交之后,指令記錄在事務(wù)日志的磁盤(pán)上,在尚未修改磁盤(pán)上的數(shù)據(jù)部分之前,事務(wù)被認(rèn)為是持續(xù)的,在系統(tǒng)正?;蚴浅霈F(xiàn)故障啟動(dòng)時(shí),SQL Server將檢查每個(gè)數(shù)據(jù)庫(kù)的事務(wù)日志并執(zhí)行具有兩個(gè)階段的恢復(fù)過(guò)程-重做和撤銷(xiāo)。可以用如下圖表示四個(gè)事務(wù)屬性。

圖片來(lái)源:https://blog./2007/12/09/sql-server-acid-atomicity-consistency-isolation-durability/
說(shuō)到事務(wù)就聯(lián)想到并發(fā),為了解決事務(wù)中的并發(fā)我們則不得不討論下鎖,所以接下來(lái)我們首先熟悉一下鎖的模式-排他鎖和共享鎖。
排他鎖:當(dāng)試圖修改數(shù)據(jù)時(shí),事務(wù)會(huì)請(qǐng)求數(shù)據(jù)資源的一個(gè)排他鎖,而不管其隔離級(jí)別,如果授予了鎖,那么排他鎖知道事務(wù)結(jié)束才會(huì)被解除,對(duì)于單語(yǔ)句事務(wù)意味著直到語(yǔ)句完成鎖定才會(huì)被解除,對(duì)于多語(yǔ)句事務(wù)意味著直到完成所有語(yǔ)句并通過(guò)COMMIT TRAN或ROLLBACK TRAN命令結(jié)束才會(huì)解除鎖定。排他鎖之所以被稱為排他,是因?yàn)槿绻粋€(gè)事務(wù)正在修改行,直到事務(wù)完成,其他事務(wù)都不能修改相同的行,這是默認(rèn)的修改行為。然而,另外一個(gè)事物能不能讀取相同的行,取決于它的隔離級(jí)別。
共享鎖:當(dāng)試圖讀取數(shù)據(jù)時(shí),事務(wù)默認(rèn)請(qǐng)求數(shù)據(jù)資源的一個(gè)共享鎖,并且一旦語(yǔ)句完成資源讀取,會(huì)立即釋放資源的共享鎖。共享鎖之所以被稱為共享,是因?yàn)槎鄠€(gè)事務(wù)可以同時(shí)持有相同資源的共享鎖。雖然在修改數(shù)據(jù)時(shí),不能修改鎖的模式和所需的持續(xù)時(shí)間,但是通過(guò)改變其隔離級(jí)別,可以在讀取數(shù)據(jù)時(shí)控制鎖定的處理方式。
講述了鎖的兩種重要的模式,那么問(wèn)題來(lái)了,鎖的存在會(huì)導(dǎo)致什么問(wèn)題?請(qǐng)繼續(xù)往下看。我們?cè)噲D去更新一條數(shù)據(jù),此時(shí)并未進(jìn)行提交
BEGIN TRAN
UPDATE Production.Products
SET unitprice += 1.00
WHERE productid = 2
接下來(lái)我們?cè)賮?lái)讀取該條記錄的數(shù)據(jù)。
SELECT
productid, unitprice
FROM Production.Products
WHERE productid = 2

接下來(lái)我們進(jìn)行查詢,此時(shí)會(huì)發(fā)現(xiàn)一直在查詢中直到達(dá)到設(shè)置的查詢超時(shí)時(shí)間為止。

當(dāng)更新行時(shí)會(huì)獲取該資源上的排他鎖,如果更新成功,SQL Server會(huì)將鎖授予會(huì)話,所以直到事務(wù)完成,其排他鎖會(huì)一直存在,當(dāng)讀取數(shù)據(jù)時(shí)需要獲取該資源上的共享鎖,但是更新行會(huì)話一直存在即以排他鎖鎖定,但是排他鎖和共享鎖不能兼容,此時(shí)會(huì)導(dǎo)致查詢阻塞不得不進(jìn)行等待。說(shuō)明鎖在并發(fā)情況下會(huì)導(dǎo)致阻塞。那么是不是不加鎖就萬(wàn)事大吉了呢?我們繼續(xù)往下看。
鎖的隔離級(jí)別
隔離級(jí)別確定了并發(fā)用戶讀取或?qū)懭氲男袨?,讀取者是任何選擇數(shù)據(jù)的語(yǔ)句,默認(rèn)情況下使用共享鎖,寫(xiě)入者是任何對(duì)表進(jìn)行修改的語(yǔ)句,并且需要一個(gè)排他鎖。在獲得鎖和鎖的持續(xù)期間,不能控制寫(xiě)入者的行為方式,但是可以控制讀取者的行為方式,我們通過(guò)設(shè)置隔離級(jí)別來(lái)隱式的影響寫(xiě)入者的行為。
SQL Server支持4個(gè)基于悲觀并發(fā)控制的傳統(tǒng)隔離級(jí)別:READ UNCOMMITTED、READ COMMITTED(企業(yè)內(nèi)部部署的SQL Server實(shí)例的默認(rèn)方式)、REPEATABLE READ、SERIALIZABLE。SQL Server還支持兩種基于并發(fā)控制(行版本)的隔離級(jí)別:SNAPSHOT和READ COMMITTED SNAPSHOT(SQL Database的默認(rèn)方式)在某種意義上,SNAPSHOT和READ COMMITTED
SNAPSHOT分別是READ COMMITTED和SERIALIZABLE的樂(lè)觀并發(fā)對(duì)應(yīng)方式。
我們使用如下命令來(lái)設(shè)置整個(gè)會(huì)話的隔離級(jí)別
SET TRANSACTION ISOLATION LEVEL <isolation name>
或者間接在表查詢中設(shè)置查詢的隔離級(jí)別。
SELECT .... FROM TABLE WITH(<isolationname>)
對(duì)于以上四個(gè)隔離級(jí)別,隔離級(jí)別越高,讀取者請(qǐng)求的鎖就越強(qiáng),并且持續(xù)時(shí)間越長(zhǎng)。因此,隔離級(jí)別越高,一致性越高并且性越低,當(dāng)然,反過(guò)來(lái)也是如此。對(duì)于兩個(gè)基于快照的隔離級(jí)別,SQL Server能夠在tempdb中存儲(chǔ)之前提交的行版本,讀取者不請(qǐng)求共享鎖。相反,如果當(dāng)前的行版本不是他們應(yīng)該看到的,SQL Server將提供給他們一個(gè)較舊的版本。
READ UNCOMMITTED隔離級(jí)別
READ COMMITTED是最低隔離級(jí)別,在該隔離級(jí)別中,讀取者不需要請(qǐng)求共享鎖,不要求共享鎖的讀取者從不會(huì)與持有排他鎖的寫(xiě)入者發(fā)生沖突,這意味著讀取者可以讀取未提交的更改即臟讀,也就是說(shuō),讀取者不會(huì)干擾要求了排他鎖的寫(xiě)入者,在該隔離級(jí)別下運(yùn)行的讀取者讀取數(shù)據(jù)時(shí),寫(xiě)入者可以更改數(shù)據(jù)。

上述我們?nèi)Τ龃藭r(shí)productid = 2的行記錄,此時(shí)我們來(lái)更新該條行記錄的uniprice列數(shù)據(jù),如下
BEGIN TRAN
UPDATE Production.Products
SET unitprice += 1.00
WHERE productid = 2;
SELECT
productid, unitprice
FROM Production.Products
WHERE productid = 2

此時(shí)我們清楚看到上述單價(jià)(unitprice)更新為了25,沒(méi)毛病,此時(shí)我們?cè)僭O(shè)置隔離級(jí)別為READ UNCOMMITTED運(yùn)行如下代碼(我們保持上述更新會(huì)話一直打開(kāi),此時(shí)將保持排他鎖一直存在,雖然排他鎖和貢獻(xiàn)鎖不兼容,但是在READ COMMITTED隔離級(jí)別下查詢不會(huì)去請(qǐng)求共享鎖,所以并不會(huì)與上述更新事務(wù)沖突)
SET TRAN ISOLATION LEVEL READ UNCOMMITTED
SELECT
productid, unitprice
FROM Production.Products
WHERE productid = 2

此時(shí)我們?cè)賹⑸鲜鑫刺峤坏闹颠M(jìn)行回滾,如下
BEGIN TRAN
UPDATE Production.Products
SET unitprice += 1.00
WHERE productid = 2;
ROLLBACK TRAN
SELECT
productid, unitprice
FROM Production.Products
WHERE productid = 2

到這里我們想必知道了臟讀的由來(lái),當(dāng)我們?cè)谝粋€(gè)會(huì)話中更新指定行記錄時(shí),此時(shí)我們并未進(jìn)行提交,此時(shí)unitprice更新為25,接著我們?cè)赗EAD COMMITTED隔離級(jí)別下去查詢同一行記錄此時(shí)查詢unitprice為25(即使上述修改并未進(jìn)行提交),最后我們?cè)谀骋粫r(shí)刻通過(guò)回滾對(duì)更新事務(wù)進(jìn)行了撤銷(xiāo),此時(shí)數(shù)據(jù)庫(kù)中的該行記錄依然是24,但是我們讀取的結(jié)果卻是25,所以 讀取者獲得的是從未提交過(guò)的值,也就是我們說(shuō)的臟讀。到這里我們可以下一個(gè)結(jié)論:
READ UNCOMMITTED:在該隔離級(jí)別下會(huì)導(dǎo)致數(shù)據(jù)臟讀。
我們通過(guò)設(shè)置隔離級(jí)別為READ COMMITTED來(lái)解決數(shù)據(jù)臟讀,請(qǐng)繼續(xù)往下看。
READ COMMITTED隔離級(jí)別
如果想阻止讀取者未提交的修改,則需要使用更高的隔離級(jí)別,防止臟讀的最低的隔離級(jí)別為READ COMMITTED,它是企業(yè)內(nèi)部部署的SQL Server默認(rèn)隔離級(jí)別,如名稱所述,該隔離級(jí)別僅允許讀取者已提交的更改。它通過(guò)要求讀取者獲得一個(gè)共享鎖來(lái)防止未提交的讀取,也就是說(shuō),如果一個(gè)寫(xiě)入者持有了排他鎖,讀取者的共享鎖請(qǐng)求將會(huì)與寫(xiě)入者沖突,此時(shí)必須等待,一旦寫(xiě)入者提交了事務(wù),讀取者就可以獲得它的共享鎖,所以它必然是只讀取提交后的修改。關(guān)于READ COMMITTED隔離級(jí)別的示例上述我們已經(jīng)演示。
問(wèn)題1:與READ COMMITTED隔離級(jí)別不同的是,在READ COMMITTED隔離級(jí)別中,不會(huì)獲得臟讀,因?yàn)樗荒茏x取已提交的修改,但是寫(xiě)入者未進(jìn)行提交此時(shí)會(huì)導(dǎo)致持續(xù)等待,對(duì)于讀取者直到完成,讀取者都僅持有共享鎖,它不會(huì)到事務(wù)結(jié)束一直持有鎖,它甚至不會(huì)到語(yǔ)句結(jié)束,換句話說(shuō),在同一事務(wù)中的兩次相同數(shù)據(jù)資源的讀取之間,不會(huì)持有該資源的鎖,所以其他事務(wù)可以在這兩次讀取的間隙修改資源,并且讀取者每次讀取到的值可能會(huì)有所不同,這種現(xiàn)象被稱為不可重復(fù)讀取或不一致解析。此時(shí)我們就必須通過(guò)更高的隔離級(jí)別來(lái)解決不可重復(fù)讀取的問(wèn)題。
問(wèn)題2:同時(shí)我們需要注意的是在READ COMMITTED隔離級(jí)別中可能出現(xiàn)【丟失更新】現(xiàn)象,丟失更新主要發(fā)生在兩個(gè)事務(wù)讀取一個(gè)值時(shí),同時(shí)基于讀取的值進(jìn)行更新,由于在該隔離級(jí)別中讀取后不會(huì)再該資源上持有鎖,兩個(gè)事務(wù)都可以更新其值,并且最后更新該值的事務(wù)將會(huì)覆蓋另外一個(gè)事務(wù)的更新。
REPEATABLE READ隔離級(jí)別
如果我們希望確保在同一事務(wù)中的多次讀取之間沒(méi)有其他事務(wù)能夠修改其值,需要提升隔離級(jí)別到REPEATABLE READ。在該隔離級(jí)別中,讀取者不僅需要一個(gè)共享鎖才能夠進(jìn)行讀取,而且直到事務(wù)結(jié)束都持有鎖,這意味著只要讀取者獲得了數(shù)據(jù)資源上的共享鎖,直到讀取者結(jié)束事務(wù),都沒(méi)有其他事務(wù)可以獲取一個(gè)排他鎖來(lái)修改資源,這樣才能保證可重復(fù)讀取或者是一致的解析。我們來(lái)演示下該隔離級(jí)別,如下:
SET TRAN ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
SELECT
productid, unitprice
FROM Production.Products
WHERE productid = 2

此時(shí)返回productid = 2的單價(jià)。接下來(lái)我們?cè)賮?lái)進(jìn)行更新。
UPDATE Production.Products
SET unitprice += 1.00
WHERE productid = 2;

由于寫(xiě)入者請(qǐng)求的排他鎖與授予讀取者的共享鎖沖突,此時(shí)寫(xiě)入者事務(wù)會(huì)被阻塞,如果讀取者是運(yùn)行在READ UNCOMMITTED或者READ COMMITTED隔離級(jí)別下,此時(shí)它將不會(huì)持有共享鎖,并且試圖修改該行就會(huì)成功。當(dāng)我們?cè)诓樵兪聞?wù)中添加COMMIT TRAN,此時(shí)讀取者的事務(wù)已經(jīng)提交并且釋放了共享鎖,如下:
SET TRAN ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
SELECT
productid, unitprice
FROM Production.Products
WHERE productid = 2
COMMIT TRAN
此時(shí)再來(lái)寫(xiě)入者就能獲取等待它的排他鎖并且成功更新行,此時(shí)unitprice = 25;
問(wèn)題1:雖然REPEATABLE READ隔離級(jí)別能夠確保同一事務(wù)中的多次讀取沒(méi)有其他事務(wù)來(lái)修改值即解決了不可重復(fù)讀取或不一致解析的問(wèn)題,但是在第一次讀取后雙方都會(huì)保持它們的共享鎖,因此對(duì)于稍后的更新都不會(huì)獲得一個(gè)排他鎖,這樣就很有可能導(dǎo)致死鎖,并且阻止更新沖突。
問(wèn)題2:雖然REPEATABLE READ隔離級(jí)別可以確保在事務(wù)中第一次讀取的行能夠重復(fù)讀取,但是事務(wù)鎖定的資源(如行)是查詢第一次運(yùn)行時(shí)發(fā)現(xiàn)的,在查詢運(yùn)行時(shí)那里并沒(méi)有行,因此,同一事務(wù)中的第二次讀取可能會(huì)返回新行,這些新行被稱為幻影,這種讀取稱為幻影讀取,如果在讀取之間,另一個(gè)事務(wù)添加了讀取者查詢篩選限定的新行,就會(huì)導(dǎo)致幻影讀取。
既然REPEATABLE READ容易導(dǎo)致幻影讀取,我們則需要更高的隔離級(jí)別來(lái)解決這個(gè)問(wèn)題,請(qǐng)繼續(xù)往下看。
SERIALIZABLE隔離級(jí)別
為了防止幻影讀取,需要將隔離級(jí)別提升為SERIALIZABLE,最重要的部分是SERIALIZABLE隔離級(jí)別的行為類(lèi)似于REPEATABLE READ即它要求讀取者獲取一個(gè)共享鎖來(lái)進(jìn)行讀取,并持有鎖到事務(wù)結(jié)束,但是SERIALIZABLE隔離級(jí)別添加了另外一個(gè)方面-在邏輯上,該隔離級(jí)別要求讀取者鎖定查詢篩選所限定的鍵的整個(gè)范圍。這意味著讀取者鎖定的不僅是查詢篩選限定的現(xiàn)有行,也包括將來(lái)行,或者準(zhǔn)確地說(shuō),它會(huì)阻止其他事務(wù)嘗試添加讀取者查詢篩選限定的行。下面我們來(lái)演示這種情況。
BEGIN TRAN
SELECT
productid, productname, categoryid, unitprice
FROM Production.Products
WHERE categoryid = 1
我們查詢產(chǎn)品Id = 1的所有行,結(jié)果集如下:

接下來(lái)我們?cè)賮?lái)插入一條數(shù)據(jù)。
INSERT INTO Production.Products
( productname ,
supplierid ,
categoryid ,
unitprice ,
discontinued
)
VALUES ( N'Product ABCDE' , -- productname - nvarchar(40)
1 , -- supplierid - int
1 , -- categoryid - int
20.00 , -- unitprice - money
0 -- discontinued - bit
)
此時(shí)嘗試插入會(huì)成功,但是查詢出來(lái)的數(shù)據(jù)有12條數(shù)據(jù),實(shí)際上有13條數(shù)據(jù)也就是說(shuō)導(dǎo)致幻影讀取。當(dāng)我們?cè)诓樵償?shù)據(jù)時(shí)設(shè)置SERIALIZABLE如下隔離級(jí)別,此時(shí)插入語(yǔ)句會(huì)將處于阻塞狀態(tài)
SET TRAN ISOLATION LEVEL SERIALIZABLE
通過(guò)設(shè)置隔離級(jí)別為SERIALIZABLE能夠解決幻影讀取情況。
基于行版本的隔離級(jí)別
在SQL Server中存在兩種基于行版本控制技術(shù)的隔離級(jí)別:SNAPSHOT、READ COMMITTED SNAPSHOT。將提交行之前的版本存儲(chǔ)在tempdb中,SNAPSHOT隔離級(jí)別在邏輯上類(lèi)似于SERIALIZABLE隔離級(jí)別,READ COMMITTED SNAPSHOT隔離級(jí)別類(lèi)似于READ COMMITTED隔離級(jí)別,但是,讀取者使用基于行版本控制的隔離級(jí)別并不不會(huì)發(fā)出共享鎖,所以在請(qǐng)求的數(shù)據(jù)以排他鎖鎖定時(shí)它們不會(huì)等待,讀取者仍舊會(huì)獲得類(lèi)似于SERIALIZABLE和READ COMMITTED的一致性級(jí)別,如果當(dāng)前版本不是它們希望看到的版本,那么SQL
Server會(huì)給讀取者提供一個(gè)較舊的版本。
如果啟用了任何基于快照的隔離級(jí)別,在修改tempdb之前,DELETE和UPDATE語(yǔ)句需要復(fù)制行的版本,對(duì)于INSERT語(yǔ)句則不需要再tempdb中版本化,因?yàn)樗淮嬖谠缙诘陌姹荆枰⒁獾氖?,啟用任何基于行版本控制的隔離級(jí)別對(duì)于數(shù)據(jù)更新和刪除的性能可能會(huì)有負(fù)面影響,由于它們不會(huì)獲取共享鎖,并且哎數(shù)據(jù)被以排他方式鎖定或是數(shù)據(jù)版本不是所期望的版本時(shí)不需要等待,因此對(duì)于讀取者的性能通常會(huì)有所改善。
SNAPSHOT隔離級(jí)別
在SNAPSHOT隔離級(jí)別下,讀取者在讀取數(shù)據(jù)時(shí), 它是確保獲得事務(wù)啟動(dòng)時(shí)最近提交的可用行版本,這意味著,保證獲得的是提交后的讀取并且可重復(fù)讀取,以及確保獲得不是幻讀,類(lèi)似于SERIALIZABLE級(jí)別中一樣,但是此隔離級(jí)別依賴于行版本,而不是使用共享鎖,要想在企業(yè)部署的SQL Server實(shí)例中允許事務(wù)以SNAPSHOT隔離級(jí)別工作,首先需要在查詢窗口執(zhí)行以下代碼打開(kāi)快照隔離級(jí)別。如下:
ALTER DATABASE TSQL2012 SET ALLOW_SNAPSHOT_ISOLATION ON
下面來(lái)演示SNAPSHOT隔離級(jí)別行為,我們打開(kāi)一個(gè)事務(wù)在當(dāng)前基礎(chǔ)上更新單價(jià),如下:
BEGIN TRAN
UPDATE Production.Products
SET unitprice += 1.00
WHERE productid = 2;
SELECT
productid, unitprice
FROM Production.Products
WHERE productid = 2

此時(shí)更新尚未提交的事務(wù),此時(shí)其單價(jià)為25。
SET TRAN ISOLATION LEVEL SNAPSHOT
BEGIN TRAN
SELECT
productid, unitprice
FROM Production.Products
WHERE productid = 2
因?yàn)槲覀冊(cè)跀?shù)據(jù)庫(kù)中啟用了SNAPSHOT隔離級(jí)別,此時(shí)即使是在READ COMMITTED隔離級(jí)別下運(yùn)行也會(huì)復(fù)制更新到tempdb之前的版本,如下我們?cè)O(shè)置隔離級(jí)別為SNAPSHOT來(lái)打開(kāi)一個(gè)事務(wù)查詢其行記錄。
SET TRAN ISOLATION LEVEL SNAPSHOT
BEGIN TRAN
SELECT
productid, unitprice
FROM Production.Products
WHERE productid = 2
如果是在SERIALIZABLE隔離級(jí)別下運(yùn)行,此時(shí)肯定導(dǎo)致查詢阻塞,但是由于在SNAPSHOT模式下,不會(huì)去獲取該事務(wù)上的共享鎖,而是獲取事務(wù)運(yùn)行時(shí)可用的上次提交的行版本。此時(shí)之前版本的unitprice = 24而不是當(dāng)前版本的unitprice = 25,如下:

此時(shí)我們?cè)賹⑸鲜鑫刺峤坏膶?xiě)入事務(wù)進(jìn)行提交。此時(shí)unitprice = 25的當(dāng)前版本則變?yōu)榱颂峤话姹荆俏覀冊(cè)賮?lái)讀取數(shù)據(jù)并提交事務(wù),仍舊會(huì)獲得該行事務(wù)啟動(dòng)時(shí)可用的最后提交版本,如下:

當(dāng)我們重新打開(kāi)一個(gè)事務(wù)進(jìn)行查詢,此時(shí)事務(wù)啟動(dòng)時(shí)該行可用的最后提交版本時(shí)unitprice = 25的版本,如下:

SNAPSHOT隔離級(jí)別可以防止更新沖突,但不會(huì)像REPEATABLE READ和SERIALIZABLE隔離級(jí)別那樣產(chǎn)生死鎖,SNAPSHOT隔離級(jí)別的事務(wù)失敗,表明檢測(cè)到了更新沖突,SNAPSHOT隔離級(jí)別通過(guò)檢查存儲(chǔ)的版本來(lái)檢測(cè)更新沖突,它可以發(fā)現(xiàn)在事務(wù)的讀取和寫(xiě)入之間是否有另一個(gè)事務(wù)修改了數(shù)據(jù)。
READ COMMITTED SNAPSHOT隔離級(jí)別
該隔離級(jí)別也是基于行版本控制,它與SNAPSHOT隔離級(jí)別區(qū)別在于,讀取者獲得是【語(yǔ)句】啟動(dòng)時(shí)可用的最后提交的行版本,而不是【事務(wù)】啟動(dòng)時(shí)可用的最后提交的行版本,READ COMMITTED SNAOSHOT也不會(huì)檢測(cè)更新沖突,導(dǎo)致類(lèi)似于READ COMMITTED隔離級(jí)別,但在所請(qǐng)求資源以排他鎖鎖定時(shí),不會(huì)請(qǐng)求共享鎖并且不會(huì)等待。在企業(yè)內(nèi)部部署的SQL Server中要想啟動(dòng)READ COMMITTED SNAPHOST隔離級(jí)別,需要打開(kāi)唯一會(huì)話來(lái)設(shè)置,否則無(wú)法進(jìn)行啟用(啟用該隔離級(jí)別實(shí)際上是將READ
COMMITTED隔離級(jí)別在語(yǔ)義上改變?yōu)镽EAD COMMITTED SNAPSHOT隔離級(jí)別)。下面我們來(lái)演示下READ COMMITTED SNAPSHOT隔離級(jí)別。
ALTER DATABASE TSQL2012 SET READ_COMMITTED_SNAPSHOT ON;
我們同樣是更新一個(gè)尚未提交的事務(wù),如下:
BEGIN TRAN;
UPDATE Production.Products
SET unitprice += 1.00
WHERE productid = 2;
SELECT
productid, unitprice
FROM Production.Products
WHERE productid = 2

此時(shí)我們將上述寫(xiě)入進(jìn)行提交,再來(lái)打開(kāi)一個(gè)會(huì)話讀取該行記錄數(shù)據(jù)。

此時(shí)我們?cè)賮?lái)提交事務(wù)看看。

如果 是在SNAPSHOT隔離級(jí)別下運(yùn)行上述代碼,就會(huì)得到unitprice = 24,但是由于代碼運(yùn)行在READ COMMITTED SNAPSHOT隔離級(jí)別下,會(huì)得到語(yǔ)句啟動(dòng)時(shí)可用的最后提交的行版本unitprice = 25,而不是事務(wù)開(kāi)始時(shí)的行版本unitprice = 24。
總結(jié)
本節(jié)比較詳細(xì)的討論了事務(wù)、四種悲觀式并發(fā)隔離級(jí)別和兩種樂(lè)觀式并發(fā)隔離級(jí)別,下節(jié)我們開(kāi)始談?wù)撘恍┘?xì)枝末節(jié)。
|