事務的隔離級別SQL Server經過在鎖資源上使用不一樣類型的鎖來隔離事務。爲了開發安全的事務,定義事務內容以及應在何種狀況下回滾相當重要,定義如何以及在多長時間內在事務中保持鎖定也同等重要。這由隔離級別決定。應用不一樣的隔離級別,SQL Server賦予開發者一種能力,讓他們爲每個單獨事務定義與其餘事務的隔離程度。事務隔離級別的定義以下:數據庫
ANSI 99定義了4種事務隔離級別,SQL Server 2005可以徹底支持這些級別:安全
此外,SQL Server還有兩種使用行版本控制來讀取數據的事務級別(本章後文將詳細檢驗這些隔離級別)。行版本控制容許一個事務在數據排他鎖定後讀取數據的最後提交版本。因爲沒必要等待到鎖釋放就可進行讀操做,所以查詢性能得以大大加強。這兩種隔離級別以下:併發
不管定義什麼隔離級別,對數據的更改老是經過排他鎖來鎖定並直到事務結束時才釋放。ide
不少狀況下,定義正確的隔離級別並非一個簡單的決定。做爲一種通用的規則,要選擇在儘量短的時間內鎖住最少數據,但同時依然能夠爲事務提供它所需的安全程度的隔離級別。工具
已提交讀性能
在SQL Server 2005中,已提交讀隔離級別是創建鏈接時的默認隔離級別。這個級別存在兩種類型:已提交讀和已提交讀快照隔離級別。應用哪一種類型由數據庫選項定義。已提交讀級別會在讀數據以前等待,直到阻塞鎖被釋放。已提交讀快照級別會在數據被其餘事務阻塞時使用行版本控制來讀數據最後一次提交的版本。版本控制
使用已提交讀級別:對象
BEGIN TRAN事務
SELECTci
FirstName, LastName, EmailAddress
FROM
Person.Contact
WHERE
ContactID = 1
返回EmailAddress爲gustavo0@adventure-works.com的聯繫人Gustavo Achong。
如今假設另外一事務在事務打開狀態下更改了EmailAddress。打開第二個查詢窗口並執行如下批來UPDATE EmailAddress,但不提交事務:
USE AdventureWorks;
BEGIN TRAN
UPDATE
Person.Contact
SET
EmailAddress = 'uncommitted@email.at'
WHERE
ContactID = 1
這個UPDATE 語句會正常運行。一行受到了影響,即便數據在這個事務尚未運行完以前已被查詢窗口1中的事務讀取。由於已提交讀級別並不會在事務結束前保持用於SELECT語句的共享鎖。共享鎖會在數據讀取以後當即被SQL Server釋放。須要一致讀的時候這將是一個問題。咱們將下面的"獲取一致的可重複讀操做"實現。
如今切換到查詢窗口1並嘗試再次讀數據:
SELECT
FirstName, LastName, EmailAddress
FROM
Person.Contact
WHERE
ContactID = 1
因爲SELECT語句被阻塞,所以這個查詢並無結束。SQL Server會嘗試在ContactID= 1的鍵上獲取一個共享鎖,可是因爲在查詢窗口2中的UPDATE語句對其有一個排他鎖,所以這個操做不可能完成。雖然查詢窗口2處於已提交讀級別(因爲您沒有更改默認級別),但排他鎖依然存在。這個阻塞將持續存在,由於數據更改的排他鎖會一直保持直到事務結束。
切換到查詢窗口2,讓查詢窗口1中的查詢繼續運行。鍵入並執行如下SELECT語句檢查數據庫中的受權和等待的鎖。
能夠看一個狀態爲WAIT的共享鎖。這是查詢窗口1中運行的查詢。它在等待查詢窗口2中的查詢,後者在一樣的資源上有一個排他鎖。
在查詢窗口2中執行一個ROLLBACK TRAN語句來回滾UPDATE語句。而後切換回查詢窗口1。能夠看到,查詢窗口1中的查詢完成了,而且其結果與之前的同樣。查詢窗口2中的事務結束的時候,鎖被釋放了,以致查詢窗口1中的查詢再也不被阻塞。因爲查詢窗口2中的事務回滾,所以查詢窗口1中獲得的結果是原來的數據。若是查詢窗口2中的事務被提交,則查詢窗口1中會獲得新的數據做爲結果。
在查詢窗口1中執行一個COMMIT TRAN語句並關閉全部的查詢窗口。
能夠看出,在(默認)已提交讀級別中SQL Server會等到排他鎖釋放以後再進行讀操做,以此來獲取真正的提交數據。還能夠看出,共享鎖會持續到數據被讀取以後,而排他鎖會持續到事務提交以後。在許多事務幾乎同時更改數據的時候這種行爲可能會形成問題。在這些狀況下,因爲排他鎖形成的阻塞,讀數據會很是慢。但在有些狀況下,使用最後提交的數據版本是恰當的。在這些狀況下,能夠將已提交讀級別更改成已提交讀快照級別。
若是要在窗口1讀取數據的話,可使用這樣的方法:
SELECT
FirstName, LastName, EmailAddress
FROM
Person.Contact WITH (NOLOCK)
WHERE
ContactID = 1
讓它取消全部的鎖機制,那麼排他鎖也不會影響到這句查詢。
使用NOLOCK注意:在 SQL Server 中,NOLOCK 提示將啓用"未提交讀"行爲。在 SQL Server Mobile 中,使用 NOLOCK 提示仍會賦予"提交讀"隔離級別。SQL Server Mobile 將維護數據副本,以確保能夠讀取數據而不須要使用共享鎖幫助保護數據。
使用已提交讀快照級別
激活已提交讀快照級別
USE master;
ALTER DATABASE AdventureWorks
SET READ_COMMITTED_SNAPSHOT ON
注意:設置 READ_COMMITTED_SNAPSHOT 選項時,數據庫中僅容許存在執行 ALTER DATABASE 命令的鏈接。在 ALTER DATABASE 完成以前,數據庫中不容許有其餘打開的鏈接。數據庫沒必要處於單用戶模式。
如今,執行如下代碼開始一個事務並像前面同樣更改EmailAddress(但要讓事務處於打開狀態):
USE AdventureWorks;
BEGIN TRAN
UPDATE Person.Contact
SET EmailAddress = 'uncommitted@email.at'
WHERE ContactID = 1;
打開第二個查詢窗口並執行如下語句來讀取ContactID 1的列Name和EmailAddress列。
USE AdventureWorks;
BEGIN TRAN
SELECT FirstName, LastName, EmailAddress
FROM Person.Contact
WHERE ContactID = 1;
返回了聯繫人Gustavo Achong的EmailAddress gustavo0@adventure-works.com,這是這一行最後提交的版本。不像沒有快照的已提交讀級別那樣,這個查詢不會被阻塞。關閉查詢窗口2並切換到查詢窗口1。
執行如下語句來回滾事務並切換回已提交讀級別(這個查詢將等待直到關閉查詢窗口2):
ROLLBACK TRAN
GO
USE master;
ALTER DATABASE AdventureWorks
SET READ_COMMITTED_SNAPSHOT OFF
重要提示 這個隔離級別能夠用於減小阻塞。但要意識到這是一個數據庫選項。當它發生了更改,將在數據庫系統中使用已提交讀級別的全部事務也會改變它們的行爲。所以,只有在全部這些事務讀最後提交的數據版本與讀真正提交的數據版本在邏輯上一樣正確的時候,使用這種級別纔是明智的。
獲取一致的可重複讀操做
已提交讀級別的一個缺點是,一個事務讀取的數據在事務運行期間可能被另外一個事務更改。所以,在兩種已提交讀級別下,不能保證一致性讀。獲取一致性讀的意思是,在一個事務中,讀取的數據始終是同樣的。
1. 已提交讀在讀數據的時候使用共享鎖,但在讀操做完成後會當即釋放這個鎖。所以,其餘事務能夠更改剛被讀過的數據。
2. 已提交讀快照讀取最後一次提交的數據版本。當它第二次讀數據的時候,最後一次提交的版本可能因爲第二個事務已經提交了對數據的更改而變成一個新版本。
在須要一致性讀的時候(例如對於報表),可能這種不一致性會致使問題。想象一下,您的事務經過數據計算了一些商業數值。在已提交讀級別中進行這種計算的時候,可能因爲基礎數據在事務計算過程當中發生了變化而致使這些值被錯誤計算。爲了成功地執行這個計算,可使用快照隔離級別。它會使用行版本管理來提供數據的提交版本,但與已提交讀快照不一樣的是,它總會提供在開始事務時最後提交的數據版本。所以,SQL Server始終會在整個事務執行過程當中獲取一樣的數據。
使用快照隔離級別
快照隔離級別須要在數據庫中一次性地激活。激活以後,每一個鏈接能夠在須要的時候使用它。
USE master;
ALTER DATABASE AdventureWorks
SET ALLOW_SNAPSHOT_ISOLATION ON;
如今假設咱們但願運行一些基於Sales.SalesOrderDetail表的報表,但須要一致性的讀操做。執行如下語句爲事務激活快照隔離級別並開始一個返回訂單行合計的事務。記住OrderTotal的值。
USE AdventureWorks;
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRAN
SELECT SUM(LineTotal) as OrderTotal
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 43659
參數SNAPSHOT的含義:
1. 指定事務中任何語句讀取的數據都將是在事務開始時便存在的數據的事務上一致的版本。事務只能識別在其開始以前提交的數據修改。在當前事務中執行的語句將看不到在當前事務開始之後由其餘事務所作的數據修改。其效果就好像事務中的語句得到了已提交數據的快照,由於該數據在事務開始時就存在。
2. 除非正在恢復數據庫,不然 SNAPSHOT 事務不會在讀取數據時請求鎖。讀取數據的 SNAPSHOT 事務不會阻止其餘事務寫入數據。寫入數據的事務也不會阻止 SNAPSHOT 事務讀取數據。
3. 在數據庫恢復的回滾階段,若是嘗試讀取由其餘正在回滾的事務鎖定的數據,則 SNAPSHOT 事務將請求一個鎖。在事務完成回滾以前,SNAPSHOT 事務會一直被阻塞。當事務取得受權以後,便會當即釋放鎖。
4. 必須將 ALLOW_SNAPSHOT_ISOLATION 數據庫選項設置爲 ON,才能開始一個使用 SNAPSHOT 隔離級別的事務。若是使用 SNAPSHOT 隔離級別的事務訪問多個數據庫中的數據,則必須在每一個數據庫中將 ALLOW_SNAPSHOT_ISOLATION 都設置爲 ON。
5. 不能將經過其餘隔離級別開始的事務設置爲 SNAPSHOT 隔離級別,不然將致使事務停止。若是一個事務在 SNAPSHOT 隔離級別開始,則能夠將它更改成另外一個隔離級別,而後再返回 SNAPSHOT。一個事務從執行 BEGIN TRANSACTION 語句開始。
6. 在 SNAPSHOT 隔離級別下運行的事務能夠查看由該事務所作的更改。例如,若是事務對錶執行 UPDATE,而後對同一個表發出 SELECT 語句,則修改後的數據將包含在結果集中。
打開第二個查詢窗口並更新SalesOrderDetail表以更改查詢窗口1中用到的基礎數據。(若是但願重複這個示例,將OrderQty的值5更改成其餘數字以使如下代碼能真正地更改數據庫中的數據):
USE AdventureWorks;
UPDATE Sales.SalesOrderDetail
SET OrderQty = 5
WHERE SalesOrderID = 43659
AND ProductID = 777
關閉查詢窗口2,切換到查詢窗口1,而後重複下面的SELECT語句。
SELECT SUM(LineTotal) as OrderTotal
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 43659
能夠看出,因爲快照隔離級別忽略了事務運行過程當中數據的更改,所以結果與之前的相同。在快照級別下總會提供在事務開始時最後提交的值。
提交這個事務並執行如下代碼再次重複這個查詢:如今可看到,因爲事務結束了,所以結果發生了變化。
COMMIT TRAN
SELECT SUM(LineTotal) as OrderTotal
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 43659
執行如下代碼關閉AdventureWorks數據庫的快照隔離級別:
ALTER DATABASE AdventureWorks
SET ALLOW_SNAPSHOT_ISOLATION OFF;
避免同時發生的數據更新
如前所述,快照隔離級別並不在讀操做的時候鎖定數據,但可以在整個事務中提供一致性的視圖。在某些狀況下,有必要在整個事務的執行過程當中鎖定數據以免其餘事務對數據的更改。假設但願爲一個訂單×××。首先須要獲取數據並檢查它,而後爲其生成發票。在這種狀況下,須要從事務起始就鎖定數據以免其餘事務更改它。在這種狀況下,快照隔離或者已提交讀隔離級別都不是好的選擇。對於這種狀況,可使用可重複讀隔離級別。這個隔離級別與沒有快照的已提交讀級別的工做過程類似,但它會保持共享鎖直至事務結束。所以,它防止了對數據的更新。
使用可重複讀隔離級別
假設但願處理OrderID爲43659的訂單。首先,必須選擇數據。爲了防止其餘事務更改正在讀的數據,使用可重複讀隔離。
USE AdventureWorks;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
SELECT SalesOrderID, SalesOrderDetailID, ProductID, OrderQty
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 43659
參數REPEATABLE READ的含義:
1. 指定語句不能讀取已由其餘事務修改但還沒有提交的行,而且指定,其餘任何事務都不能在當前事務完成以前修改由當前事務讀取的數據。
2. 對事務中的每一個語句所讀取的所有數據都設置了共享鎖,而且該共享鎖一直保持到事務完成爲止。這樣能夠防止其餘事務修改當前事務讀取的任何行。其餘事務能夠插入與當前事務所發出語句的搜索條件相匹配的新行。若是當前事務隨後重試執行該語句,它會檢索新行,從而產生幻讀。因爲共享鎖一直保持到事務結束,而不是在每一個語句結束時釋放,因此併發級別低於默認的 READ COMMITTED 隔離級別。此選項只在必要時使用。
打開第二個查詢窗口並執行如下代碼嘗試更新SalesOrderDetail表以更改查詢窗口1中要使用的基礎數據:
UPDATE Sales.SalesOrderDetail
SET OrderQty = 5
WHERE SalesOrderID = 43659
AND ProductID = 777
查詢會等待。不像快照隔離級別,不可能更新數據,由於共享鎖會保持以防止其餘事務更改數據。這個鎖能夠經過前面用過的管理視圖sys.dm_tran_locks查看。
單擊工具條上的"取消執行查詢"按鈕取消在查詢窗口2中的查詢。而執行如下INSERT語句在訂單中加入一個新行項。
INSERT INTO Sales.SalesOrderDetail
(
SalesOrderID,
CarrierTrackingNumber,
OrderQty,
ProductID,
SpecialOfferID,
UnitPrice,
UnitPriceDiscount
)
VALUES(43659,'4911-403C-98',1,758,1,874,0)
注意,即便正處於可重複讀隔離級別,這個語句也會成功執行。由於可重複讀會鎖定數據以阻止數據的更新,但INSERT語句向數據庫中插入新數據,這是容許的。新行處於查詢窗口1中事務SELECT語句的查詢範圍之中,因此會在事務下一次獲取相同數據的時候被讀取到。這稱做幻像讀。
重複SELECT語句並提交這個事務,以下所示:
SELECT SalesOrderID, SalesOrderDetailID, ProductID, OrderQty
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 43659
COMMIT TRAN
能夠觀察到,新行被SELECT語句讀取了,由於它處於這個語句的查詢範圍以內。可重複讀級別會阻止現有數據被更改,但不會阻止新數據插入SELECT語句的查詢範圍內。
其餘
SET TRANSACTION一共有如下幾種級別:
SET TRANSACTION ISOLATION LEVEL
{ READ UNCOMMITTED
| READ COMMITTED
| REPEATABLE READ
| SNAPSHOT
| SERIALIZABLE
}
[ ; ]
上面的例子中沒有提到的幾種隔離級別的說明:
指定語句能夠讀取已由其餘事務修改但還沒有提交的行。
在 READ UNCOMMITTED 級別運行的事務,不會發出共享鎖來防止其餘事務修改當前事務讀取的數據。READ UNCOMMITTED 事務也不會被排他鎖阻塞,排他鎖會禁止當前事務讀取其餘事務已修改但還沒有提交的行。設置此選項以後,能夠讀取未提交的修改,這種讀取稱爲髒讀。在事務結束以前,能夠更改數據中的值,行也能夠出如今數據集中或從數據集中消失。該選項的做用與在事務內全部 SELECT 語句中的全部表上設置 NOLOCK 相同。這是隔離級別中限制最少的級別。
在 SQL Server 2005 中,您還可使用下列任意一種方法,在保護事務不髒讀未提交的數據修改的同時儘可能減小鎖定爭用:
1. READ COMMITTED 隔離級別,並將 READ_COMMITTED_SNAPSHOT 數據庫選項設置爲 ON。
2. SNAPSHOT 隔離級別。
指定語句不能讀取已由其餘事務修改但還沒有提交的數據。這樣能夠避免髒讀。其餘事務能夠在當前事務的各個語句之間更改數據,從而產生不可重複讀取和幻像數據。該選項是 SQL Server 的默認設置。
READ COMMITTED 的行爲取決於 READ_COMMITTED_SNAPSHOT 數據庫選項的設置:
1. 若是將 READ_COMMITTED_SNAPSHOT 設置爲 OFF(默認設置),則數據庫引擎 會使用共享鎖防止其餘事務在當前事務執行讀取操做期間修改行。共享鎖還會阻止語句在其餘事務完成以前讀取由這些事務修改的行。語句完成後便會釋放共享鎖。
2. 若是將 READ_COMMITTED_SNAPSHOT 設置爲 ON,則數據庫引擎 會使用行版本控制爲每一個語句提供一個在事務上一致的數據快照,由於該數據在語句開始時就存在。不使用鎖來防止其餘事務更新數據。
當 READ_COMMITTED_SNAPSHOT 數據庫選項設置爲 ON 時,您可使用 READCOMMITTEDLOCK 表提示爲 READ_COMMITTED 隔離級別上運行的事務中的各語句請求共享鎖,而不是行版本控制。
注意:設置 READ_COMMITTED_SNAPSHOT 選項時,數據庫中僅容許存在執行 ALTER DATABASE 命令的鏈接。在 ALTER DATABASE 完成以前,數據庫中不容許有其餘打開的鏈接。數據庫沒必要處於單用戶模式。
請指定下列內容:
1
事務的隔離級別
SQL Server經過在鎖資源上使用不一樣類型的鎖來隔離事務。爲了開發安全的事務,定義事務內容以及應在何種狀況下回滾相當重要,定義如何以及在多長時間內在事務中保持鎖定也同等重要。這由隔離級別決定。應用不一樣的隔離級別,SQL Server賦予開發者一種能力,讓他們爲每個單獨事務定義與其餘事務的隔離程度。事務隔離級別的定義以下:
ANSI 99定義了4種事務隔離級別,SQL Server 2005可以徹底支持這些級別:
此外,SQL Server還有兩種使用行版本控制來讀取數據的事務級別(本章後文將詳細檢驗這些隔離級別)。行版本控制容許一個事務在數據排他鎖定後讀取數據的最後提交版本。因爲沒必要等待到鎖釋放就可進行讀操做,所以查詢性能得以大大加強。這兩種隔離級別以下:
不管定義什麼隔離級別,對數據的更改老是經過排他鎖來鎖定並直到事務結束時才釋放。
不少狀況下,定義正確的隔離級別並非一個簡單的決定。做爲一種通用的規則,要選擇在儘量短的時間內鎖住最少數據,但同時依然能夠爲事務提供它所需的安全程度的隔離級別。
已提交讀
在SQL Server 2005中,已提交讀隔離級別是創建鏈接時的默認隔離級別。這個級別存在兩種類型:已提交讀和已提交讀快照隔離級別。應用哪一種類型由數據庫選項定義。已提交讀級別會在讀數據以前等待,直到阻塞鎖被釋放。已提交讀快照級別會在數據被其餘事務阻塞時使用行版本控制來讀數據最後一次提交的版本。
使用已提交讀級別:
BEGIN TRAN
SELECT
FirstName, LastName, EmailAddress
FROM
Person.Contact
WHERE
ContactID = 1
返回EmailAddress爲gustavo0@adventure-works.com的聯繫人Gustavo Achong。
如今假設另外一事務在事務打開狀態下更改了EmailAddress。打開第二個查詢窗口並執行如下批來UPDATE EmailAddress,但不提交事務:
USE AdventureWorks;
BEGIN TRAN
UPDATE
Person.Contact
SET
EmailAddress = 'uncommitted@email.at'
WHERE
ContactID = 1
這個UPDATE 語句會正常運行。一行受到了影響,即便數據在這個事務尚未運行完以前已被查詢窗口1中的事務讀取。由於已提交讀級別並不會在事務結束前保持用於SELECT語句的共享鎖。共享鎖會在數據讀取以後當即被SQL Server釋放。須要一致讀的時候這將是一個問題。咱們將下面的"獲取一致的可重複讀操做"實現。
如今切換到查詢窗口1並嘗試再次讀數據:
SELECT
FirstName, LastName, EmailAddress
FROM
Person.Contact
WHERE
ContactID = 1
因爲SELECT語句被阻塞,所以這個查詢並無結束。SQL Server會嘗試在ContactID= 1的鍵上獲取一個共享鎖,可是因爲在查詢窗口2中的UPDATE語句對其有一個排他鎖,所以這個操做不可能完成。雖然查詢窗口2處於已提交讀級別(因爲您沒有更改默認級別),但排他鎖依然存在。這個阻塞將持續存在,由於數據更改的排他鎖會一直保持直到事務結束。
切換到查詢窗口2,讓查詢窗口1中的查詢繼續運行。鍵入並執行如下SELECT語句檢查數據庫中的受權和等待的鎖。
能夠看一個狀態爲WAIT的共享鎖。這是查詢窗口1中運行的查詢。它在等待查詢窗口2中的查詢,後者在一樣的資源上有一個排他鎖。
在查詢窗口2中執行一個ROLLBACK TRAN語句來回滾UPDATE語句。而後切換回查詢窗口1。能夠看到,查詢窗口1中的查詢完成了,而且其結果與之前的同樣。查詢窗口2中的事務結束的時候,鎖被釋放了,以致查詢窗口1中的查詢再也不被阻塞。因爲查詢窗口2中的事務回滾,所以查詢窗口1中獲得的結果是原來的數據。若是查詢窗口2中的事務被提交,則查詢窗口1中會獲得新的數據做爲結果。
在查詢窗口1中執行一個COMMIT TRAN語句並關閉全部的查詢窗口。
能夠看出,在(默認)已提交讀級別中SQL Server會等到排他鎖釋放以後再進行讀操做,以此來獲取真正的提交數據。還能夠看出,共享鎖會持續到數據被讀取以後,而排他鎖會持續到事務提交以後。在許多事務幾乎同時更改數據的時候這種行爲可能會形成問題。在這些狀況下,因爲排他鎖形成的阻塞,讀數據會很是慢。但在有些狀況下,使用最後提交的數據版本是恰當的。在這些狀況下,能夠將已提交讀級別更改成已提交讀快照級別。
若是要在窗口1讀取數據的話,可使用這樣的方法:
SELECT
FirstName, LastName, EmailAddress
FROM
Person.Contact WITH (NOLOCK)
WHERE
ContactID = 1
讓它取消全部的鎖機制,那麼排他鎖也不會影響到這句查詢。
使用NOLOCK注意:在 SQL Server 中,NOLOCK 提示將啓用"未提交讀"行爲。在 SQL Server Mobile 中,使用 NOLOCK 提示仍會賦予"提交讀"隔離級別。SQL Server Mobile 將維護數據副本,以確保能夠讀取數據而不須要使用共享鎖幫助保護數據。
使用已提交讀快照級別
激活已提交讀快照級別
USE master;
ALTER DATABASE AdventureWorks
SET READ_COMMITTED_SNAPSHOT ON
注意:設置 READ_COMMITTED_SNAPSHOT 選項時,數據庫中僅容許存在執行 ALTER DATABASE 命令的鏈接。在 ALTER DATABASE 完成以前,數據庫中不容許有其餘打開的鏈接。數據庫沒必要處於單用戶模式。
如今,執行如下代碼開始一個事務並像前面同樣更改EmailAddress(但要讓事務處於打開狀態):
USE AdventureWorks;
BEGIN TRAN
UPDATE Person.Contact
SET EmailAddress = 'uncommitted@email.at'
WHERE ContactID = 1;
打開第二個查詢窗口並執行如下語句來讀取ContactID 1的列Name和EmailAddress列。
USE AdventureWorks;
BEGIN TRAN
SELECT FirstName, LastName, EmailAddress
FROM Person.Contact
WHERE ContactID = 1;
返回了聯繫人Gustavo Achong的EmailAddress gustavo0@adventure-works.com,這是這一行最後提交的版本。不像沒有快照的已提交讀級別那樣,這個查詢不會被阻塞。關閉查詢窗口2並切換到查詢窗口1。
執行如下語句來回滾事務並切換回已提交讀級別(這個查詢將等待直到關閉查詢窗口2):
ROLLBACK TRAN
GO
USE master;
ALTER DATABASE AdventureWorks
SET READ_COMMITTED_SNAPSHOT OFF
重要提示 這個隔離級別能夠用於減小阻塞。但要意識到這是一個數據庫選項。當它發生了更改,將在數據庫系統中使用已提交讀級別的全部事務也會改變它們的行爲。所以,只有在全部這些事務讀最後提交的數據版本與讀真正提交的數據版本在邏輯上一樣正確的時候,使用這種級別纔是明智的。
獲取一致的可重複讀操做
已提交讀級別的一個缺點是,一個事務讀取的數據在事務運行期間可能被另外一個事務更改。所以,在兩種已提交讀級別下,不能保證一致性讀。獲取一致性讀的意思是,在一個事務中,讀取的數據始終是同樣的。
1. 已提交讀在讀數據的時候使用共享鎖,但在讀操做完成後會當即釋放這個鎖。所以,其餘事務能夠更改剛被讀過的數據。
2. 已提交讀快照讀取最後一次提交的數據版本。當它第二次讀數據的時候,最後一次提交的版本可能因爲第二個事務已經提交了對數據的更改而變成一個新版本。
在須要一致性讀的時候(例如對於報表),可能這種不一致性會致使問題。想象一下,您的事務經過數據計算了一些商業數值。在已提交讀級別中進行這種計算的時候,可能因爲基礎數據在事務計算過程當中發生了變化而致使這些值被錯誤計算。爲了成功地執行這個計算,可使用快照隔離級別。它會使用行版本管理來提供數據的提交版本,但與已提交讀快照不一樣的是,它總會提供在開始事務時最後提交的數據版本。所以,SQL Server始終會在整個事務執行過程當中獲取一樣的數據。
使用快照隔離級別
快照隔離級別須要在數據庫中一次性地激活。激活以後,每一個鏈接能夠在須要的時候使用它。
USE master;
ALTER DATABASE AdventureWorks
SET ALLOW_SNAPSHOT_ISOLATION ON;
如今假設咱們但願運行一些基於Sales.SalesOrderDetail表的報表,但須要一致性的讀操做。執行如下語句爲事務激活快照隔離級別並開始一個返回訂單行合計的事務。記住OrderTotal的值。
USE AdventureWorks;
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
BEGIN TRAN
SELECT SUM(LineTotal) as OrderTotal
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 43659
參數SNAPSHOT的含義:
1. 指定事務中任何語句讀取的數據都將是在事務開始時便存在的數據的事務上一致的版本。事務只能識別在其開始以前提交的數據修改。在當前事務中執行的語句將看不到在當前事務開始之後由其餘事務所作的數據修改。其效果就好像事務中的語句得到了已提交數據的快照,由於該數據在事務開始時就存在。
2. 除非正在恢復數據庫,不然 SNAPSHOT 事務不會在讀取數據時請求鎖。讀取數據的 SNAPSHOT 事務不會阻止其餘事務寫入數據。寫入數據的事務也不會阻止 SNAPSHOT 事務讀取數據。
3. 在數據庫恢復的回滾階段,若是嘗試讀取由其餘正在回滾的事務鎖定的數據,則 SNAPSHOT 事務將請求一個鎖。在事務完成回滾以前,SNAPSHOT 事務會一直被阻塞。當事務取得受權以後,便會當即釋放鎖。
4. 必須將 ALLOW_SNAPSHOT_ISOLATION 數據庫選項設置爲 ON,才能開始一個使用 SNAPSHOT 隔離級別的事務。若是使用 SNAPSHOT 隔離級別的事務訪問多個數據庫中的數據,則必須在每一個數據庫中將 ALLOW_SNAPSHOT_ISOLATION 都設置爲 ON。
5. 不能將經過其餘隔離級別開始的事務設置爲 SNAPSHOT 隔離級別,不然將致使事務停止。若是一個事務在 SNAPSHOT 隔離級別開始,則能夠將它更改成另外一個隔離級別,而後再返回 SNAPSHOT。一個事務從執行 BEGIN TRANSACTION 語句開始。
6. 在 SNAPSHOT 隔離級別下運行的事務能夠查看由該事務所作的更改。例如,若是事務對錶執行 UPDATE,而後對同一個表發出 SELECT 語句,則修改後的數據將包含在結果集中。
打開第二個查詢窗口並更新SalesOrderDetail表以更改查詢窗口1中用到的基礎數據。(若是但願重複這個示例,將OrderQty的值5更改成其餘數字以使如下代碼能真正地更改數據庫中的數據):
USE AdventureWorks;
UPDATE Sales.SalesOrderDetail
SET OrderQty = 5
WHERE SalesOrderID = 43659
AND ProductID = 777
關閉查詢窗口2,切換到查詢窗口1,而後重複下面的SELECT語句。
SELECT SUM(LineTotal) as OrderTotal
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 43659
能夠看出,因爲快照隔離級別忽略了事務運行過程當中數據的更改,所以結果與之前的相同。在快照級別下總會提供在事務開始時最後提交的值。
提交這個事務並執行如下代碼再次重複這個查詢:如今可看到,因爲事務結束了,所以結果發生了變化。
COMMIT TRAN
SELECT SUM(LineTotal) as OrderTotal
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 43659
執行如下代碼關閉AdventureWorks數據庫的快照隔離級別:
ALTER DATABASE AdventureWorks
SET ALLOW_SNAPSHOT_ISOLATION OFF;
避免同時發生的數據更新
如前所述,快照隔離級別並不在讀操做的時候鎖定數據,但可以在整個事務中提供一致性的視圖。在某些狀況下,有必要在整個事務的執行過程當中鎖定數據以免其餘事務對數據的更改。假設但願爲一個訂單×××。首先須要獲取數據並檢查它,而後爲其生成發票。在這種狀況下,須要從事務起始就鎖定數據以免其餘事務更改它。在這種狀況下,快照隔離或者已提交讀隔離級別都不是好的選擇。對於這種狀況,可使用可重複讀隔離級別。這個隔離級別與沒有快照的已提交讀級別的工做過程類似,但它會保持共享鎖直至事務結束。所以,它防止了對數據的更新。
使用可重複讀隔離級別
假設但願處理OrderID爲43659的訂單。首先,必須選擇數據。爲了防止其餘事務更改正在讀的數據,使用可重複讀隔離。
USE AdventureWorks;
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
BEGIN TRAN
SELECT SalesOrderID, SalesOrderDetailID, ProductID, OrderQty
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 43659
參數REPEATABLE READ的含義:
1. 指定語句不能讀取已由其餘事務修改但還沒有提交的行,而且指定,其餘任何事務都不能在當前事務完成以前修改由當前事務讀取的數據。
2. 對事務中的每一個語句所讀取的所有數據都設置了共享鎖,而且該共享鎖一直保持到事務完成爲止。這樣能夠防止其餘事務修改當前事務讀取的任何行。其餘事務能夠插入與當前事務所發出語句的搜索條件相匹配的新行。若是當前事務隨後重試執行該語句,它會檢索新行,從而產生幻讀。因爲共享鎖一直保持到事務結束,而不是在每一個語句結束時釋放,因此併發級別低於默認的 READ COMMITTED 隔離級別。此選項只在必要時使用。
打開第二個查詢窗口並執行如下代碼嘗試更新SalesOrderDetail表以更改查詢窗口1中要使用的基礎數據:
UPDATE Sales.SalesOrderDetail
SET OrderQty = 5
WHERE SalesOrderID = 43659
AND ProductID = 777
查詢會等待。不像快照隔離級別,不可能更新數據,由於共享鎖會保持以防止其餘事務更改數據。這個鎖能夠經過前面用過的管理視圖sys.dm_tran_locks查看。
單擊工具條上的"取消執行查詢"按鈕取消在查詢窗口2中的查詢。而執行如下INSERT語句在訂單中加入一個新行項。
INSERT INTO Sales.SalesOrderDetail
(
SalesOrderID,
CarrierTrackingNumber,
OrderQty,
ProductID,
SpecialOfferID,
UnitPrice,
UnitPriceDiscount
)
VALUES(43659,'4911-403C-98',1,758,1,874,0)
注意,即便正處於可重複讀隔離級別,這個語句也會成功執行。由於可重複讀會鎖定數據以阻止數據的更新,但INSERT語句向數據庫中插入新數據,這是容許的。新行處於查詢窗口1中事務SELECT語句的查詢範圍之中,因此會在事務下一次獲取相同數據的時候被讀取到。這稱做幻像讀。
重複SELECT語句並提交這個事務,以下所示:
SELECT SalesOrderID, SalesOrderDetailID, ProductID, OrderQty
FROM Sales.SalesOrderDetail
WHERE SalesOrderID = 43659
COMMIT TRAN
能夠觀察到,新行被SELECT語句讀取了,由於它處於這個語句的查詢範圍以內。可重複讀級別會阻止現有數據被更改,但不會阻止新數據插入SELECT語句的查詢範圍內。
其餘
SET TRANSACTION一共有如下幾種級別:
SET TRANSACTION ISOLATION LEVEL
{ READ UNCOMMITTED
| READ COMMITTED
| REPEATABLE READ
| SNAPSHOT
| SERIALIZABLE
}
[ ; ]
上面的例子中沒有提到的幾種隔離級別的說明:
指定語句能夠讀取已由其餘事務修改但還沒有提交的行。
在 READ UNCOMMITTED 級別運行的事務,不會發出共享鎖來防止其餘事務修改當前事務讀取的數據。READ UNCOMMITTED 事務也不會被排他鎖阻塞,排他鎖會禁止當前事務讀取其餘事務已修改但還沒有提交的行。設置此選項以後,能夠讀取未提交的修改,這種讀取稱爲髒讀。在事務結束以前,能夠更改數據中的值,行也能夠出如今數據集中或從數據集中消失。該選項的做用與在事務內全部 SELECT 語句中的全部表上設置 NOLOCK 相同。這是隔離級別中限制最少的級別。
在 SQL Server 2005 中,您還可使用下列任意一種方法,在保護事務不髒讀未提交的數據修改的同時儘可能減小鎖定爭用:
1. READ COMMITTED 隔離級別,並將 READ_COMMITTED_SNAPSHOT 數據庫選項設置爲 ON。
2. SNAPSHOT 隔離級別。
指定語句不能讀取已由其餘事務修改但還沒有提交的數據。這樣能夠避免髒讀。其餘事務能夠在當前事務的各個語句之間更改數據,從而產生不可重複讀取和幻像數據。該選項是 SQL Server 的默認設置。
READ COMMITTED 的行爲取決於 READ_COMMITTED_SNAPSHOT 數據庫選項的設置:
1. 若是將 READ_COMMITTED_SNAPSHOT 設置爲 OFF(默認設置),則數據庫引擎 會使用共享鎖防止其餘事務在當前事務執行讀取操做期間修改行。共享鎖還會阻止語句在其餘事務完成以前讀取由這些事務修改的行。語句完成後便會釋放共享鎖。
2. 若是將 READ_COMMITTED_SNAPSHOT 設置爲 ON,則數據庫引擎 會使用行版本控制爲每一個語句提供一個在事務上一致的數據快照,由於該數據在語句開始時就存在。不使用鎖來防止其餘事務更新數據。
當 READ_COMMITTED_SNAPSHOT 數據庫選項設置爲 ON 時,您可使用 READCOMMITTEDLOCK 表提示爲 READ_COMMITTED 隔離級別上運行的事務中的各語句請求共享鎖,而不是行版本控制。
注意:設置 READ_COMMITTED_SNAPSHOT 選項時,數據庫中僅容許存在執行 ALTER DATABASE 命令的鏈接。在 ALTER DATABASE 完成以前,數據庫中不容許有其餘打開的鏈接。數據庫沒必要處於單用戶模式。
請指定下列內容:
1. 語句不能讀取已由其餘事務修改但還沒有提交的數據。
2. 任何其餘事務都不能在當前事務完成以前修改由當前事務讀取的數據。
3. 在當前事務完成以前,其餘事務不能使用當前事務中任何語句讀取的鍵值插入新行。
範圍鎖處於與事務中執行的每一個語句的搜索條件相匹配的鍵值範圍以內。這樣能夠阻止其餘事務更新或插入任何行,從而限定當前事務所執行的任何語句。這意味着若是再次執行事務中的任何語句,則這些語句便會讀取同一組行。在事務完成以前將一直保持範圍鎖。這是限制最多的隔離級別,由於它鎖定了鍵的整個範圍,並在事務完成以前一直保持範圍鎖。由於併發級別較低,因此應只在必要時才使用該選項。該選項的做用與在事務內全部 SELECT 語句中的全部表上設置 HOLDLOCK 相同。
須要注意的地方:
1. 一次只能設置一個隔離級別選項,並且設置的選項將一直對那個鏈接始終有效,直到顯式更改該選項爲止。事務中執行的全部讀取操做都會在指定的隔離級別的規則下運行,除非語句的 FROM 子句中的表提示爲表指定了其餘鎖定行爲或版本控制行爲。
2. 事務隔離級別定義了可爲讀取操做獲取的鎖類型。針對 READ COMMITTED 或 REPEATABLE READ 獲取的共享鎖一般爲行鎖,儘管當讀取引用了頁或表中大量的行時,行鎖能夠升級爲頁鎖或表鎖。若是某行在被讀取以後由事務進行了修改,則該事務會獲取一個用於保護該行的排他鎖,而且該排他鎖在事務完成以前將一直保持。例如,若是 REPEATABLE READ 事務具備用於某行的共享鎖,而且該事務隨後修改了該行,則共享行鎖便會轉換爲排他行鎖。
3. 在事務進行期間,能夠隨時將事務從一個隔離級別切換到另外一個隔離級別,但有一種狀況例外。即在從任一隔離級別更改到 SNAPSHOT 隔離時,不能進行上述操做。不然會致使事務失敗並回滾。可是,能夠將在 SNAPSHOT 隔離中啓動的事務更改成任何其餘隔離級別。
4. 將事務從一個隔離級別更改成另外一個隔離級別以後,便會根據新級別的規則對更改後讀取的資源執行保護。在更改前讀取的資源將繼續按照之前級別的規則受到保護。例如,若是某個事務從 READ COMMITTED 更改成 SERIALIZABLE,則在該事務結束前,更改後所獲取的共享鎖將一直處於保留狀態。
5. 若是在存儲過程或觸發器中發出 SET TRANSACTION ISOLATION LEVEL,則當對象返回控制時,隔離級別會重設爲在調用對象時有效的級別。例如,若是在批處理中設置 REPEATABLE READ,而且該批處理調用一個將隔離級別設置爲 SERIALIZABLE 的存儲過程,則當該存儲過程將控制返回給該批處理時,隔離級別就會恢復爲 REPEATABLE READ。
. 語句不能讀取已由其餘事務修改但還沒有提交的數據。
2. 任何其餘事務都不能在當前事務完成以前修改由當前事務讀取的數據。
3. 在當前事務完成以前,其餘事務不能使用當前事務中任何語句讀取的鍵值插入新行。
範圍鎖處於與事務中執行的每一個語句的搜索條件相匹配的鍵值範圍以內。這樣能夠阻止其餘事務更新或插入任何行,從而限定當前事務所執行的任何語句。這意味着若是再次執行事務中的任何語句,則這些語句便會讀取同一組行。在事務完成以前將一直保持範圍鎖。這是限制最多的隔離級別,由於它鎖定了鍵的整個範圍,並在事務完成以前一直保持範圍鎖。由於併發級別較低,因此應只在必要時才使用該選項。該選項的做用與在事務內全部 SELECT 語句中的全部表上設置 HOLDLOCK 相同。
須要注意的地方:
1. 一次只能設置一個隔離級別選項,並且設置的選項將一直對那個鏈接始終有效,直到顯式更改該選項爲止。事務中執行的全部讀取操做都會在指定的隔離級別的規則下運行,除非語句的 FROM 子句中的表提示爲表指定了其餘鎖定行爲或版本控制行爲。
2. 事務隔離級別定義了可爲讀取操做獲取的鎖類型。針對 READ COMMITTED 或 REPEATABLE READ 獲取的共享鎖一般爲行鎖,儘管當讀取引用了頁或表中大量的行時,行鎖能夠升級爲頁鎖或表鎖。若是某行在被讀取以後由事務進行了修改,則該事務會獲取一個用於保護該行的排他鎖,而且該排他鎖在事務完成以前將一直保持。例如,若是 REPEATABLE READ 事務具備用於某行的共享鎖,而且該事務隨後修改了該行,則共享行鎖便會轉換爲排他行鎖。
3. 在事務進行期間,能夠隨時將事務從一個隔離級別切換到另外一個隔離級別,但有一種狀況例外。即在從任一隔離級別更改到 SNAPSHOT 隔離時,不能進行上述操做。不然會致使事務失敗並回滾。可是,能夠將在 SNAPSHOT 隔離中啓動的事務更改成任何其餘隔離級別。
4. 將事務從一個隔離級別更改成另外一個隔離級別以後,便會根據新級別的規則對更改後讀取的資源執行保護。在更改前讀取的資源將繼續按照之前級別的規則受到保護。例如,若是某個事務從 READ COMMITTED 更改成 SERIALIZABLE,則在該事務結束前,更改後所獲取的共享鎖將一直處於保留狀態。
5. 若是在存儲過程或觸發器中發出 SET TRANSACTION ISOLATION LEVEL,則當對象返回控制時,隔離級別會重設爲在調用對象時有效的級別。例如,若是在批處理中設置 REPEATABLE READ,而且該批處理調用一個將隔離級別設置爲 SERIALIZABLE 的存儲過程,則當該存儲過程將控制返回給該批處理時,隔離級別就會恢復爲 REPEATABLE READ。