事务与并发
開場白還是要來下的,又過了一年,很久沒有和cnblogs朋友見面了。我還看到了我上幾篇MSSQL的回復里面一直說期待我的下一個好文,唉!先謝罪了,太久沒發表東東了。主要是因為有點忙,加上一回去就是學習,所以沒時間給大家整理資料。最近因為要給公司培訓一些東西,所以以后也會出一些文章的,所謂濃縮就是精華,所以大家就期待吧。
???????? 其實寫文章總結也是對自己知識的一些反復,總結的時候可以查漏補缺,也可以給各位博友們提供一些看法和思路。我記得看過一篇架構師的曲線圖里面也講到了“寫文章”總結的重要性。
???????? 今天開場白有點多,大家耐心點。呵呵,其實一直想寫一個關于如何學習東西的文章,只有下次了,好了,那么我們進入主題(我會結合MSDN講)。
鳥瞰
a)???????? 應用程序事務與數據庫存儲引擎事務
b)???????? 應用程序處理并發與數據庫存儲引擎處理并發
為什么要從兩個方向來考慮呢,是有原因的,首先機制處理方式,以及在軟件中的所處的環境以及情節都不同。所以我們可以將事務與并發分為兩個部分來講,這文主要講MSSQL的事務,我會在接下來的文章談談java或者C#處理事務以及結合Martin Fowler說到的并發架構模式探討。
l 什么是事務呢?
切看MSDN給出的定義:事務是作為單個邏輯工作單元執行的一系列操作。一個邏輯工作單元必須有四個屬性,稱為原子性、一致性、隔離性和持久性 (ACID) 屬性,只有這樣才能成為一個事務。
???????? 大家不要小看這些定義,往往從最基礎的定義才能進入最深層次的東西,我經??吹揭恍〤SDN網友或者cnblogs里面的朋友,大放闕詞,說改使用啥啥啥“鎖提示”,改用啥啥啥“事務”最完美,其實都是不正確的。如果對事務不的ACID不熟悉的話,可以看看
ms-help://MS.MSDNQTR.v90.chs/udb9/html/c193ad34-be19-408a-a0fa-9723a7936a3c.htm(安裝了本地MSDN)。
l 什么是并發呢?
當多個用戶同時訪問數據時,那么在這種情況下就叫做并發呢。
好了,上述定義都很直觀、簡單,相信大多數朋友早就知道了這些東西。
???????? 接著,我們先談并發,MS SQL SERVER 2005給了我們兩種方式來處理并發,正如我們所熟悉的使用事務隔離級別,和鎖提示(不清楚定義的,可以一會在下文看到)等。了解原理的朋友們應該知道,上述都是基于“資源鎖定“的。還有一種處理并發的機制,可能大家不太了解,就是基于”行版本控制的“,說白了就是維護一個行的副本,進行的一些處理機制。
???????? 那么我們在看看MSDN的定義,
·???????? 鎖定
每個事務對所依賴的資源(如行、頁或表)請求不同類型的鎖。鎖可以阻止其他事務以某種可能會導致事務請求鎖出錯的方式修改資源。當事務不再依賴鎖定的資源時,它將釋放鎖。
·???????? 行版本控制
當啟用了基于行版本控制的隔離級別時,數據庫引擎 將維護修改的每一行的版本。應用程序可以指定事務使用行版本查看事務或查詢開始時存在的數據,而不是使用鎖保護所有讀取。通過使用行版本控制,讀取操作阻止其他事務的可能性將大大降低。
?
當我們在MSSQL中不使用這寫方法,就可能導致一些意外的結果,所以我們可以看看一些異常的情況。(我會引用許多微軟的原文,大家見諒了。我主要負責解釋難懂的地方,一些提供一些代碼)
測試環境:MS SQL SERVER 2005
測試工具: SSMS
先執行下面的代碼,建立測試環境:
SET NOCOUNT ON
GO
?
USE [Master]
GO
?
DECLARE @Path VARCHAR(MAX);
?
?
SET @Path=(SELECT SUBSTRING(physical_name,1,CHARINDEX('master.mdf',physical_name)-1) FROM sys.master_files WHERE file_id=1 AND database_id=1);
?
SELECT @Path AS 'Your database folder'
?
?
--Detect whether the database is exist
IF DB_ID('GoodGoodStudyDayDayUp') IS NOT NULL
??? DROP DATABASE GoodGoodStudyDayDayUp;
GO
?
--Create the test-driven database
CREATE DATABASE GoodGoodStudyDayDayUp;
GO
?
--Change the context
USE GoodGoodStudyDayDayUp;
GO
l 丟失更新
當兩個或多個事務選擇同一行,然后基于最初選定的值更新該行時,會發生丟失更新問題。每個事務都不知道其他事務的存在。最后的更新將覆蓋由其他事務所做的更新,這將導致數據丟失。例如,兩個編輯人員制作了同一文檔的電子副本。每個編輯人員獨立地更改其副本,然后保存更改后的副本,這樣就覆蓋了原始文檔。最后保存其更改副本的編輯人員覆蓋另一個編輯人員所做的更改。如果在一個編輯人員完成并提交事務之前,另一個編輯人員不能訪問同一文件,則可避免此問題。
?
很顯然的,如果你和你同事同時修改一個代碼,當你們都搞定準備提交的時候,那么可怕的事情就發生了,因為晚提交的往往才勝利,他將會覆蓋你的版本,意味著你白忙活了。同樣的,可以把這種事情隱射到許多情況之下。
l 未提交讀(臟讀)(Example 1)
當第二個事務選擇其他事務正在更新的行時,會發生未提交的依賴關系問題。第二個事務正在讀取的數據還沒有提交并且可能由更新此行的事務所更改。
?
例如,一個編輯人員正在更改電子文檔。在更改過程中,另一個編輯人員復制了該文檔(該副本包含到目前為止所做的全部更改)并將其分發給預期的用戶。此后,第一個編輯人員認為目前所做的更改是錯誤的,于是刪除了所做的編輯并保存了文檔。分發給用戶的文檔包含不再存在的編輯內容,并且這些編輯內容應視為從未存在過。如果在第一個編輯人員保存最終更改并提交事務之前,任何人都不能讀取更改的文檔,則可以避免此問題。
T-SQL演示:
會話1:
USE [GoodGoodStudyDayDayUp]
GO
?
BEGIN TRAN
?
--編輯人員開始讀取第一條記錄
SELECT * FROM dbo.Test1 WHERE TestID = 1
?
--最終修改編輯
UPDATE dbo.Test1 SET String='AAA' WHERE TestID = 1
?
WAITFOR DELAY '00:00:04';
?
--發現錯誤了,取消提交
ROLLBACK
會話2:
USE [GoodGoodStudyDayDayUp]
GO
?
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
?
--編輯人員開始讀取第一條記錄,然后發給其他人
SELECT * FROM dbo.Test1 WHERE TestID = 1
?
--或者直接運行
?
SELECT * FROM dbo.Test1 WITH(NOLOCK) WHERE TestID=1
說明:新建兩個查詢,在查詢1、2中分別復制上述代碼,然后先運行1,再運行2。
我們發現,會話一先更新了記錄,然后延時4秒以后,回滾,那么最終的記錄還是A,而會話2就在更新的同時馬上讀取了讀取(也就是未提交的數據,所以要設置隔離級別為READ UNCOMMITTED就是為提交的意思)臟數據,因為會話一還沒確認提交。
??? 那么,這里頭又有什么蹊蹺了,為什么我使用的NOLOCK“鎖提示”(再一次出現,不急),和設置隔離級別為臟讀取就好了呢?嘿嘿,要成為高手,就要細心看到,什么!@!#@,我是菜鳥,呃。。。。
??? 那其中又用到了什么呢,其實我
們在看到“鎖提示”就暗示著使用的是鎖定來控制的并發。
微軟為我們提供了一系列的視圖和存儲過程來觀察鎖定。
T-SQL:
USE GoodGoodStudyDayDayUp;
GO
--2000
SP_LOCK
?
-2005
SELECT * FROM sys.dm_tran_locks
回到剛才的問題,我們來觀察下,在會話1的時候,都獲取了一些什么鎖。
先執行會話一,馬上執行會話三(就是查詢鎖定信息的會話)
結果:
resource_type??????????????????????? request_mode
PAGE????????????????????????????????????????????????????????????????????????? IX
OBJECT??????????????????????????????????????????????????????????????????? IX
KEY???????????????????????????????????????????????????????????????????????????????????? X
?
結果顯示在更新的事務中,分別請求了資源PAGE,OBJECT,KEY上的意向排他鎖,意向排他鎖和排他鎖。鎖的概念我一個個解釋,我們先鎖定KEY來看看,KEY(鍵值),我們更新的時候是通過主鍵鎖定的記錄,所以請求的鎖資源就是KEY,那么request_mode就表示請求的什么鎖類型,X就表示排他鎖,大家請謹記哦,所謂排他,我們可以在MSDN的鎖兼容性中看到他與所有其他種類的鎖都不兼容,那意味著什么呢?當我們在某種資源上請求了一種鎖的時候,那么就不能獲取任何其他的鎖。
下面做個簡單的測試:
我們只要修改一下我們剛才的代碼,去掉
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
以及WITH(NOLOCK)。
那么我們先執行會話一進行更新,現在KEY上面請求了一個排他鎖,那么馬上執行修改后的會話二,在執行會話三查詢所有請求的鎖資源。
結果:
PAGE???????? IS
PAGE???????? IX
OBJECT??? IS
OBJECT??? IX
KEY????????? X
KEY????????? S
我們發現,在頁級別,OBJECT對象級別,以及KEY級別都多了一個鎖,IS,S他們分別是意向共享鎖和共享鎖,我先解釋下共享鎖,當我們執行查詢語句的時候,都會請求一個共享鎖(共享資源的嘛)。
所以 會話二發生鎖等待就能解釋了,因為會話一獲取了KEY(ID=1)資源上的排他鎖,而會話二想獲取KEY上面的共享鎖,剛才前面說了,X排他鎖不和任何其他鎖兼容,所以會話二就會被阻止。
???????? 那么,我們再來看看官方對S,X鎖的定義:
共享 (S)
用于不更改或不更新數據的讀取操作,如 SELECT 語句。
排他 (X)
用于數據修改操作,例如 INSERT、UPDATE 或 DELETE。確保不會同時對同一資源進行多重更新。
所以就得到了解釋,我們會話一致性的UPDATE語句,嘿嘿,你很聰明,可能也猜到了為什么設置了隔離級別和鎖提示不會阻止會話二了,設置為“允許臟讀(讀取未提交)”隔離級別的時候,是不會請求資源上的共享鎖的,那么測試下,把剛才的那些會話修改過來,進行測試:
PAGE???????? IX
OBJECT??? IX
KEY????????? X
所以當我們使用
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED和WITH(NOLOCK)
的時候不請求資源上的共享鎖,還有一種情況不會請求共享鎖,就是行版本控制技術。一會和大家說。
???????? 通過上面的例子,我想大家,應該明白了一些東西,什么時候會請求共享鎖,什么時候會請求排他鎖了吧。先和大家解釋個概念“鎖提示”:可以在 SELECT、INSERT、UPDATE 及 DELETE 語句中為單個表引用指定鎖提示。提示指定 Microsoft SQL Server 數據庫引擎實例用于表數據的鎖類型或行版本控制。當需要對對象所獲得鎖類型進行更精細控制時,可以使用表級鎖提示。這些鎖提示覆蓋會話的當前事務隔離級別。我們再看下微軟的建議:
注意:
數據庫引擎查詢優化器幾乎總是會選擇正確的鎖級別。建議只在必要時才使用表級鎖提示來更改默認的鎖行為。禁止鎖級別反過來會影響并發。
所以你不是一個DBA專家的話,就不要使用鎖定提示了哈。
???????? OK,告一段落,大家腦袋里面構思下,在看看前面做的測試,我提供了下載,在Example 1文件夾中。在想的差不多的時候,看下如下定義,那是MSDN的官方定義,我總結的在好,和官方總有一些出入,看清楚最原始的,我的只是便于理解。大家認真看下吧:(我已經從MSDN提出來了)
鎖定是 Microsoft SQL Server 數據庫引擎 用來同步多個用戶同時對同一個數據塊的訪問的一種機制。
在事務獲取數據塊當前狀態的依賴關系(比如通過讀取或修改數據)之前,它必須保護自己不受其他事務對同一數據進行修改的影響。事務通過請求鎖定數據塊來達到此目的。鎖有多種模式,如共享或獨占。鎖模式定義了事務對數據所擁有的依賴關系級別。如果某個事務已獲得特定數據的鎖,則其他事務不能獲得會與該鎖模式發生沖突的鎖。如果事務請求的鎖模式與已授予同一數據的鎖發生沖突,則數據庫引擎 實例將暫停事務請求直到第一個鎖釋放。
當事務修改某個數據塊時,它將持有保護所做修改的鎖直到事務結束。事務持有(所獲取的用來保護讀取操作的)鎖的時間長度,取決于事務隔離級別設置。一個事務持有的所有鎖都在事務完成(無論是提交還是回滾)時釋放。
應用程序一般不直接請求鎖。鎖由數據庫引擎 的一個部件(稱為“鎖管理器”)在內部管理。當數據庫引擎 實例處理 Transact-SQL 語句時,數據庫引擎 查詢處理器會決定將要訪問哪些資源。查詢處理器根據訪問類型和事務隔離級別設置來確定保護每一資源所需的鎖的類型。然后,查詢處理器將向鎖管理器請求適當的鎖。如果與其他事務所持有的鎖不會發生沖突,鎖管理器將授予該鎖。
????? 當你看到這里的時候,是不是清醒了很多,先恭喜下你又長進了。那么繼續吧。。。。剛才的學習測試中,我們看到了其他的一些鎖,比如IX,IS等等。他們又是什么呢?
????? Microsoft SQL Server 數據庫引擎 具有多粒度鎖定,允許一個事務鎖定不同類型的資源。為了盡量減少鎖定的開銷,數據庫引擎 自動將資源鎖定在適合任務的級別。鎖定在較小的粒度(例如行)可以提高并發度,但開銷較高,因為如果鎖定了許多行,則需要持有更多的鎖。鎖定在較大的粒度(例如表)會降低了并發度,因為鎖定整個表限制了其他事務對表中任意部分的訪問。但其開銷較低,因為需要維護的鎖較少。
數據庫引擎 通常必須獲取多粒度級別上的鎖才能完整地保護資源。這組多粒度級別上的鎖稱為鎖層次結構。例如,為了完整地保護對索引的讀取,數據庫引擎 實例可能必須獲取行上的共享鎖以及頁和表上的意向共享鎖。關于鎖粒度和層次結果可以看看這里。
????? 關于每種鎖的用途的,可以看看這里和每種鎖之間的兼容行。我強烈建議大家,記熟悉用熟悉這些鎖的用途,當真正的了解到以后,才能更好的做資源并發處理。如果你沒有看這個鎖用途,那么接下來的文章,你將話費更大的力氣,所以看完了再繼續。對了還有關于數據庫引擎中的隔離級別
l 不一致的分析(不可重復讀)(Example 2)
當第二個事務多次訪問同一行而且每次讀取不同的數據時,會發生不一致的分析問題。不一致的分析與未提交的依賴關系類似,因為其他事務也是正在更改第二個事務正在讀取的數據。但是,在不一致的分析中,第二個事務讀取的數據是由已進行了更改的事務提交的。此外,不一致的分析涉及多次(兩次或更多)讀取同一行,而且每次信息都被其他事務更改,因此我們稱之為“不可重復讀”。
例如,編輯人員兩次讀取同一文檔,但在兩次讀取之間,作者重寫了該文檔。當編輯人員第二次讀取文檔時,文檔已更改。原始讀取不可重復。如果在編輯人員完成最后一次讀取文檔之前,作者不能更改文檔,則可以避免此問題。
同樣的,我們用T-SQL來模擬這種環境:
會話一:
/*
?
Author:
DateTime:2009.06.15 20:13
Location:
Description:不可重復讀測試會話一
?
*/
USE GoodGoodStudyDayDayUp;
GO
?
BEGIN TRAN
--編輯人員讀取了一條記錄
SELECT * FROM dbo.Test1 WHERE TestID = 1
?
--他處理一些東西,上了個廁所,等待了秒
WAITFOR DELAY '00:00:05'
?
--處理完以后,再次查詢
SELECT * FROM dbo.Test1 WHERE TestID = 1
?
COMMIT
會話2:
/*
?
Author:浪客
DateTime:2009.06.15 20:13
Location:成都
Description:不可重復讀測試會話二
?
*/
?
BEGIN TRAN
?
--編輯人員,在會話一的同時修改了數據
UPDATE dbo.Test1 SET String='AA' WHERE TestID=1
?
COMMIT TRAN
?
--還原記錄
--UPDATE dbo.Test1 SET String='A' WHERE TestID=1
會話3:
/*
?
Author:浪客
DateTime:2009.06.15 20:13
Location:成都
Description:不可重復讀測試會話三
?
*/
?
USE GoodGoodStudyDayDayUp;
GO
?
SELECT * FROM sys.dm_tran_locks
?
我們分別執行會話1,2,3。
會話一執行要等待5秒,立即執行會話2,因為會話1中的查詢語句獲取的共享鎖是一瞬間的事情,查詢完成以后馬上就釋放了,所以會話2不發生鎖等待,直接更新成功,然后執行會話3,因為會話一的共享鎖可能已經釋放,而會話2更新也是一瞬間的事情,所以將獲取不到其他的鎖,只能獲取3個數據庫的共享鎖,因為開啟了3個會話。(你當然可以在會話2中放置WAITFOR DELAY來延時,那么在會話3中觀察到X鎖,自己測試吧。)
最后會話的結果為:
TestID????? String
----------- --------------------
1?????????? A
?
(1 row(s) affected)
?
TestID????? String
----------- --------------------
1?????????? AA
?
(1 row(s) affected)
很明顯的,出現了MSDN描述的不可重復讀的問題。編輯人員兩次讀取同一文檔,但在兩次讀取之間,作者重寫了該文檔。當編輯人員第二次讀取文檔時,文檔已更改。
如果你看了前面我推薦的關于鎖的文章,數據庫引擎中的隔離級別這個的話,那么你應該知道“已提交讀”READ COMMITTED是不能防止這種問題的,為什么呢?請問大屏幕。
已提交讀的原理是,在有更新的資源上要去獲取S共享鎖。但是,這是的情況是,先進行查詢,資源獲取共享鎖以后,立即釋放,然后這個時候會話2更新資源,當會話一繼續的時候,那么讀取的就是更新后的數據了。也就是說,防止臟讀的隔離級別,只能保證數據在取的那一瞬間,是保證是其他事務已經提交的數據,否則發生鎖等待,等待其他事務/會話釋放X鎖,然后交給當前事務獲取S共享鎖,所以在一瞬間中,不能保證整個事務讀取的東西都不不受其他事務影響,已提交讀,只保證自己獲取的數據是別人已經提交的了。
????? 那么下面,我通過申明“可重復讀“來防止這個問題:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
在會話一設置以后,運行會話一,立即運行會話3觀察下獲取的鎖,然后運行會話2,在運行行會話3。
會話3分別顯示的結果是:
第一次:
PAGE?????? IS
OBJECT???? IS
KEY???????? S
?
第二次:
PAGE??? IX
PAGE??? IS
OBJECT IX
OBJECT IS
KEY???? S
KEY???? X
第一次結果顯示說明了,當我們指定了這個隔離級別的時候,將在所指定的資源中獲取共享鎖,而且會一直持續到事務接觸,那么就意味著,我們的會話二肯定會受到阻塞,因為他要獲取資源的排他鎖??梢詮牡?次的結果中看出來,那么也就解決了當編輯人員1處理文檔的過程中一致都不會受其他會話的影響,使多次讀取的結果不一致。
我們再來看看MSDN對這個隔離級別的詳細定義: 指定語句不能讀取已由其他事務修改但尚未提交的行,并且指定,其他任何事務都不能在當前事務完成之前修改由當前事務讀取的數據。對事務中的每個語句所讀取的全部數據都設置了共享鎖,并且該共享鎖一直保持到事務完成為止。這樣可以防止其他事務修改當前事務讀取的任何行。其他事務可以插入與當前事務所發出語句的搜索條件相匹配的新行。如果當前事務隨后重試執行該語句,它會檢索新行,從而產生幻讀。由于共享鎖一直保持到事務結束,而不是在每個語句結束時釋放,所以并發級別低于默認的 READ COMMITTED 隔離級別。此選項只在必要時使用。
紅色部分也就證明我們剛才說是放置讀取資源的共享鎖一直到事務結束的猜測是正確的。同時他提到了,這種方式不能防止幻讀。那么我們就來研究一下這個東西。
當對某行執行插入或刪除操作,而該行屬于某個事務正在讀取的行的范圍時,會發生幻讀問題。由于其他事務的刪除操作,事務第一次讀取的行的范圍顯示有一行不再存在于第二次或后續讀取內容中。同樣,由于其他事務的插入操作,事務第二次或后續讀取的內容顯示有一行并不存在于原始讀取內容中。
l 幻讀(Example 3)
例如,一個編輯人員更改作者提交的文檔,但當生產部門將其更改內容合并到該文檔的主副本時,發現作者已將未編輯的新材料添加到該文檔中。與不可重復讀的情況相似,如果在編輯人員和生產部門完成對原始文檔的處理之前,任何人都不能將新材料添加到文檔中,則可以避免此問題。
我們還是用3個會話來模擬這種環境:
會話一:
/*
?
Author:浪客
DateTime:2009.06.15 20:13
Location:成都
Description:幻讀測試會話一
?
*/
?
--先刪除ID為的這條記錄
--DELETE FROM dbo.Test1 WHERE TestID=5
?
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
?
SELECT * FROM dbo.Test1 WHERE TestID<=7
WAITFOR DELAY '00:00:05'
?
COMMIT
會話2:
/*
?
Author:浪客
DateTime:2009.06.15 20:13
Location:成都
Description:幻讀測試會話二
?
*/
?
SET IDENTITY_INSERT dbo.Test1 ON
INSERT INTO dbo.Test1(TestID,String) VALUES(5,'E');
SET IDENTITY_INSERT dbo.Test1 OFF
會話3:
/*
?
Author:浪客
DateTime:2009.06.15 20:13
Location:成都
Description:幻讀測試會話三
?
*/
?
USE GoodGoodStudyDayDayUp;
GO
?
SELECT * FROM sys.dm_tran_locks
我們分別運行會話1,會話3,會話2,會話3。那么3最終的結果為:
PAGE??? IX
PAGE??? IS
KEY???? RangeS-S
?
KEY RangeI-N
OBJECT IX
OBJECT IS
KEY???? RangeS-S
KEY???? RangeS-S
KEY???? RangeS-S
KEY???? RangeS-S
KEY???? RangeS-S
KEY???? RangeS-S
KEY???? RangeS-S
我們看到結果中存在一個RangeI-N鎖,我們來看下MSDN的定義:
RangeI
Null
RangeI-N
插入范圍,空資源鎖;用于在索引中插入新鍵之前測試范圍。
再看下我們的測試中,其實有一條記錄是不存在的,所以也會有一個這樣的鎖。關于范圍鎖定看看這里
?
好了,幾種情況也和大家描述了,具體的情況還是要讀者們自己去研究,畢竟要全部說清楚還是要自己去領悟對吧。
暫時就說到這里,其他的以后想寫文章了幫大家總結,其實所有東西MSDN講述的已經夠清楚了,只不過有點凌亂,呵呵:)。
這個文章是草稿,是給公司做培訓用的,修改好的不能發哈,不好意思了
/Files/bhtfg538/T-SQL.rar
???????? 其實寫文章總結也是對自己知識的一些反復,總結的時候可以查漏補缺,也可以給各位博友們提供一些看法和思路。我記得看過一篇架構師的曲線圖里面也講到了“寫文章”總結的重要性。
???????? 今天開場白有點多,大家耐心點。呵呵,其實一直想寫一個關于如何學習東西的文章,只有下次了,好了,那么我們進入主題(我會結合MSDN講)。
鳥瞰
a)???????? 應用程序事務與數據庫存儲引擎事務
b)???????? 應用程序處理并發與數據庫存儲引擎處理并發
為什么要從兩個方向來考慮呢,是有原因的,首先機制處理方式,以及在軟件中的所處的環境以及情節都不同。所以我們可以將事務與并發分為兩個部分來講,這文主要講MSSQL的事務,我會在接下來的文章談談java或者C#處理事務以及結合Martin Fowler說到的并發架構模式探討。
l 什么是事務呢?
切看MSDN給出的定義:事務是作為單個邏輯工作單元執行的一系列操作。一個邏輯工作單元必須有四個屬性,稱為原子性、一致性、隔離性和持久性 (ACID) 屬性,只有這樣才能成為一個事務。
???????? 大家不要小看這些定義,往往從最基礎的定義才能進入最深層次的東西,我經??吹揭恍〤SDN網友或者cnblogs里面的朋友,大放闕詞,說改使用啥啥啥“鎖提示”,改用啥啥啥“事務”最完美,其實都是不正確的。如果對事務不的ACID不熟悉的話,可以看看
ms-help://MS.MSDNQTR.v90.chs/udb9/html/c193ad34-be19-408a-a0fa-9723a7936a3c.htm(安裝了本地MSDN)。
l 什么是并發呢?
當多個用戶同時訪問數據時,那么在這種情況下就叫做并發呢。
好了,上述定義都很直觀、簡單,相信大多數朋友早就知道了這些東西。
???????? 接著,我們先談并發,MS SQL SERVER 2005給了我們兩種方式來處理并發,正如我們所熟悉的使用事務隔離級別,和鎖提示(不清楚定義的,可以一會在下文看到)等。了解原理的朋友們應該知道,上述都是基于“資源鎖定“的。還有一種處理并發的機制,可能大家不太了解,就是基于”行版本控制的“,說白了就是維護一個行的副本,進行的一些處理機制。
???????? 那么我們在看看MSDN的定義,
·???????? 鎖定
每個事務對所依賴的資源(如行、頁或表)請求不同類型的鎖。鎖可以阻止其他事務以某種可能會導致事務請求鎖出錯的方式修改資源。當事務不再依賴鎖定的資源時,它將釋放鎖。
·???????? 行版本控制
當啟用了基于行版本控制的隔離級別時,數據庫引擎 將維護修改的每一行的版本。應用程序可以指定事務使用行版本查看事務或查詢開始時存在的數據,而不是使用鎖保護所有讀取。通過使用行版本控制,讀取操作阻止其他事務的可能性將大大降低。
?
當我們在MSSQL中不使用這寫方法,就可能導致一些意外的結果,所以我們可以看看一些異常的情況。(我會引用許多微軟的原文,大家見諒了。我主要負責解釋難懂的地方,一些提供一些代碼)
測試環境:MS SQL SERVER 2005
測試工具: SSMS
先執行下面的代碼,建立測試環境:
SET NOCOUNT ON
GO
?
USE [Master]
GO
?
DECLARE @Path VARCHAR(MAX);
?
?
SET @Path=(SELECT SUBSTRING(physical_name,1,CHARINDEX('master.mdf',physical_name)-1) FROM sys.master_files WHERE file_id=1 AND database_id=1);
?
SELECT @Path AS 'Your database folder'
?
?
--Detect whether the database is exist
IF DB_ID('GoodGoodStudyDayDayUp') IS NOT NULL
??? DROP DATABASE GoodGoodStudyDayDayUp;
GO
?
--Create the test-driven database
CREATE DATABASE GoodGoodStudyDayDayUp;
GO
?
--Change the context
USE GoodGoodStudyDayDayUp;
GO
l 丟失更新
當兩個或多個事務選擇同一行,然后基于最初選定的值更新該行時,會發生丟失更新問題。每個事務都不知道其他事務的存在。最后的更新將覆蓋由其他事務所做的更新,這將導致數據丟失。例如,兩個編輯人員制作了同一文檔的電子副本。每個編輯人員獨立地更改其副本,然后保存更改后的副本,這樣就覆蓋了原始文檔。最后保存其更改副本的編輯人員覆蓋另一個編輯人員所做的更改。如果在一個編輯人員完成并提交事務之前,另一個編輯人員不能訪問同一文件,則可避免此問題。
?
很顯然的,如果你和你同事同時修改一個代碼,當你們都搞定準備提交的時候,那么可怕的事情就發生了,因為晚提交的往往才勝利,他將會覆蓋你的版本,意味著你白忙活了。同樣的,可以把這種事情隱射到許多情況之下。
l 未提交讀(臟讀)(Example 1)
當第二個事務選擇其他事務正在更新的行時,會發生未提交的依賴關系問題。第二個事務正在讀取的數據還沒有提交并且可能由更新此行的事務所更改。
?
例如,一個編輯人員正在更改電子文檔。在更改過程中,另一個編輯人員復制了該文檔(該副本包含到目前為止所做的全部更改)并將其分發給預期的用戶。此后,第一個編輯人員認為目前所做的更改是錯誤的,于是刪除了所做的編輯并保存了文檔。分發給用戶的文檔包含不再存在的編輯內容,并且這些編輯內容應視為從未存在過。如果在第一個編輯人員保存最終更改并提交事務之前,任何人都不能讀取更改的文檔,則可以避免此問題。
T-SQL演示:
會話1:
USE [GoodGoodStudyDayDayUp]
GO
?
BEGIN TRAN
?
--編輯人員開始讀取第一條記錄
SELECT * FROM dbo.Test1 WHERE TestID = 1
?
--最終修改編輯
UPDATE dbo.Test1 SET String='AAA' WHERE TestID = 1
?
WAITFOR DELAY '00:00:04';
?
--發現錯誤了,取消提交
ROLLBACK
會話2:
USE [GoodGoodStudyDayDayUp]
GO
?
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
?
--編輯人員開始讀取第一條記錄,然后發給其他人
SELECT * FROM dbo.Test1 WHERE TestID = 1
?
--或者直接運行
?
SELECT * FROM dbo.Test1 WITH(NOLOCK) WHERE TestID=1
說明:新建兩個查詢,在查詢1、2中分別復制上述代碼,然后先運行1,再運行2。
我們發現,會話一先更新了記錄,然后延時4秒以后,回滾,那么最終的記錄還是A,而會話2就在更新的同時馬上讀取了讀取(也就是未提交的數據,所以要設置隔離級別為READ UNCOMMITTED就是為提交的意思)臟數據,因為會話一還沒確認提交。
??? 那么,這里頭又有什么蹊蹺了,為什么我使用的NOLOCK“鎖提示”(再一次出現,不急),和設置隔離級別為臟讀取就好了呢?嘿嘿,要成為高手,就要細心看到,什么!@!#@,我是菜鳥,呃。。。。
??? 那其中又用到了什么呢,其實我
們在看到“鎖提示”就暗示著使用的是鎖定來控制的并發。
微軟為我們提供了一系列的視圖和存儲過程來觀察鎖定。
T-SQL:
USE GoodGoodStudyDayDayUp;
GO
--2000
SP_LOCK
?
-2005
SELECT * FROM sys.dm_tran_locks
回到剛才的問題,我們來觀察下,在會話1的時候,都獲取了一些什么鎖。
先執行會話一,馬上執行會話三(就是查詢鎖定信息的會話)
結果:
resource_type??????????????????????? request_mode
PAGE????????????????????????????????????????????????????????????????????????? IX
OBJECT??????????????????????????????????????????????????????????????????? IX
KEY???????????????????????????????????????????????????????????????????????????????????? X
?
結果顯示在更新的事務中,分別請求了資源PAGE,OBJECT,KEY上的意向排他鎖,意向排他鎖和排他鎖。鎖的概念我一個個解釋,我們先鎖定KEY來看看,KEY(鍵值),我們更新的時候是通過主鍵鎖定的記錄,所以請求的鎖資源就是KEY,那么request_mode就表示請求的什么鎖類型,X就表示排他鎖,大家請謹記哦,所謂排他,我們可以在MSDN的鎖兼容性中看到他與所有其他種類的鎖都不兼容,那意味著什么呢?當我們在某種資源上請求了一種鎖的時候,那么就不能獲取任何其他的鎖。
下面做個簡單的測試:
我們只要修改一下我們剛才的代碼,去掉
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
以及WITH(NOLOCK)。
那么我們先執行會話一進行更新,現在KEY上面請求了一個排他鎖,那么馬上執行修改后的會話二,在執行會話三查詢所有請求的鎖資源。
結果:
PAGE???????? IS
PAGE???????? IX
OBJECT??? IS
OBJECT??? IX
KEY????????? X
KEY????????? S
我們發現,在頁級別,OBJECT對象級別,以及KEY級別都多了一個鎖,IS,S他們分別是意向共享鎖和共享鎖,我先解釋下共享鎖,當我們執行查詢語句的時候,都會請求一個共享鎖(共享資源的嘛)。
所以 會話二發生鎖等待就能解釋了,因為會話一獲取了KEY(ID=1)資源上的排他鎖,而會話二想獲取KEY上面的共享鎖,剛才前面說了,X排他鎖不和任何其他鎖兼容,所以會話二就會被阻止。
???????? 那么,我們再來看看官方對S,X鎖的定義:
共享 (S)
用于不更改或不更新數據的讀取操作,如 SELECT 語句。
排他 (X)
用于數據修改操作,例如 INSERT、UPDATE 或 DELETE。確保不會同時對同一資源進行多重更新。
所以就得到了解釋,我們會話一致性的UPDATE語句,嘿嘿,你很聰明,可能也猜到了為什么設置了隔離級別和鎖提示不會阻止會話二了,設置為“允許臟讀(讀取未提交)”隔離級別的時候,是不會請求資源上的共享鎖的,那么測試下,把剛才的那些會話修改過來,進行測試:
PAGE???????? IX
OBJECT??? IX
KEY????????? X
所以當我們使用
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED和WITH(NOLOCK)
的時候不請求資源上的共享鎖,還有一種情況不會請求共享鎖,就是行版本控制技術。一會和大家說。
???????? 通過上面的例子,我想大家,應該明白了一些東西,什么時候會請求共享鎖,什么時候會請求排他鎖了吧。先和大家解釋個概念“鎖提示”:可以在 SELECT、INSERT、UPDATE 及 DELETE 語句中為單個表引用指定鎖提示。提示指定 Microsoft SQL Server 數據庫引擎實例用于表數據的鎖類型或行版本控制。當需要對對象所獲得鎖類型進行更精細控制時,可以使用表級鎖提示。這些鎖提示覆蓋會話的當前事務隔離級別。我們再看下微軟的建議:
注意:
數據庫引擎查詢優化器幾乎總是會選擇正確的鎖級別。建議只在必要時才使用表級鎖提示來更改默認的鎖行為。禁止鎖級別反過來會影響并發。
所以你不是一個DBA專家的話,就不要使用鎖定提示了哈。
???????? OK,告一段落,大家腦袋里面構思下,在看看前面做的測試,我提供了下載,在Example 1文件夾中。在想的差不多的時候,看下如下定義,那是MSDN的官方定義,我總結的在好,和官方總有一些出入,看清楚最原始的,我的只是便于理解。大家認真看下吧:(我已經從MSDN提出來了)
鎖定是 Microsoft SQL Server 數據庫引擎 用來同步多個用戶同時對同一個數據塊的訪問的一種機制。
在事務獲取數據塊當前狀態的依賴關系(比如通過讀取或修改數據)之前,它必須保護自己不受其他事務對同一數據進行修改的影響。事務通過請求鎖定數據塊來達到此目的。鎖有多種模式,如共享或獨占。鎖模式定義了事務對數據所擁有的依賴關系級別。如果某個事務已獲得特定數據的鎖,則其他事務不能獲得會與該鎖模式發生沖突的鎖。如果事務請求的鎖模式與已授予同一數據的鎖發生沖突,則數據庫引擎 實例將暫停事務請求直到第一個鎖釋放。
當事務修改某個數據塊時,它將持有保護所做修改的鎖直到事務結束。事務持有(所獲取的用來保護讀取操作的)鎖的時間長度,取決于事務隔離級別設置。一個事務持有的所有鎖都在事務完成(無論是提交還是回滾)時釋放。
應用程序一般不直接請求鎖。鎖由數據庫引擎 的一個部件(稱為“鎖管理器”)在內部管理。當數據庫引擎 實例處理 Transact-SQL 語句時,數據庫引擎 查詢處理器會決定將要訪問哪些資源。查詢處理器根據訪問類型和事務隔離級別設置來確定保護每一資源所需的鎖的類型。然后,查詢處理器將向鎖管理器請求適當的鎖。如果與其他事務所持有的鎖不會發生沖突,鎖管理器將授予該鎖。
????? 當你看到這里的時候,是不是清醒了很多,先恭喜下你又長進了。那么繼續吧。。。。剛才的學習測試中,我們看到了其他的一些鎖,比如IX,IS等等。他們又是什么呢?
????? Microsoft SQL Server 數據庫引擎 具有多粒度鎖定,允許一個事務鎖定不同類型的資源。為了盡量減少鎖定的開銷,數據庫引擎 自動將資源鎖定在適合任務的級別。鎖定在較小的粒度(例如行)可以提高并發度,但開銷較高,因為如果鎖定了許多行,則需要持有更多的鎖。鎖定在較大的粒度(例如表)會降低了并發度,因為鎖定整個表限制了其他事務對表中任意部分的訪問。但其開銷較低,因為需要維護的鎖較少。
數據庫引擎 通常必須獲取多粒度級別上的鎖才能完整地保護資源。這組多粒度級別上的鎖稱為鎖層次結構。例如,為了完整地保護對索引的讀取,數據庫引擎 實例可能必須獲取行上的共享鎖以及頁和表上的意向共享鎖。關于鎖粒度和層次結果可以看看這里。
????? 關于每種鎖的用途的,可以看看這里和每種鎖之間的兼容行。我強烈建議大家,記熟悉用熟悉這些鎖的用途,當真正的了解到以后,才能更好的做資源并發處理。如果你沒有看這個鎖用途,那么接下來的文章,你將話費更大的力氣,所以看完了再繼續。對了還有關于數據庫引擎中的隔離級別
l 不一致的分析(不可重復讀)(Example 2)
當第二個事務多次訪問同一行而且每次讀取不同的數據時,會發生不一致的分析問題。不一致的分析與未提交的依賴關系類似,因為其他事務也是正在更改第二個事務正在讀取的數據。但是,在不一致的分析中,第二個事務讀取的數據是由已進行了更改的事務提交的。此外,不一致的分析涉及多次(兩次或更多)讀取同一行,而且每次信息都被其他事務更改,因此我們稱之為“不可重復讀”。
例如,編輯人員兩次讀取同一文檔,但在兩次讀取之間,作者重寫了該文檔。當編輯人員第二次讀取文檔時,文檔已更改。原始讀取不可重復。如果在編輯人員完成最后一次讀取文檔之前,作者不能更改文檔,則可以避免此問題。
同樣的,我們用T-SQL來模擬這種環境:
會話一:
/*
?
Author:
DateTime:2009.06.15 20:13
Location:
Description:不可重復讀測試會話一
?
*/
USE GoodGoodStudyDayDayUp;
GO
?
BEGIN TRAN
--編輯人員讀取了一條記錄
SELECT * FROM dbo.Test1 WHERE TestID = 1
?
--他處理一些東西,上了個廁所,等待了秒
WAITFOR DELAY '00:00:05'
?
--處理完以后,再次查詢
SELECT * FROM dbo.Test1 WHERE TestID = 1
?
COMMIT
會話2:
/*
?
Author:浪客
DateTime:2009.06.15 20:13
Location:成都
Description:不可重復讀測試會話二
?
*/
?
BEGIN TRAN
?
--編輯人員,在會話一的同時修改了數據
UPDATE dbo.Test1 SET String='AA' WHERE TestID=1
?
COMMIT TRAN
?
--還原記錄
--UPDATE dbo.Test1 SET String='A' WHERE TestID=1
會話3:
/*
?
Author:浪客
DateTime:2009.06.15 20:13
Location:成都
Description:不可重復讀測試會話三
?
*/
?
USE GoodGoodStudyDayDayUp;
GO
?
SELECT * FROM sys.dm_tran_locks
?
我們分別執行會話1,2,3。
會話一執行要等待5秒,立即執行會話2,因為會話1中的查詢語句獲取的共享鎖是一瞬間的事情,查詢完成以后馬上就釋放了,所以會話2不發生鎖等待,直接更新成功,然后執行會話3,因為會話一的共享鎖可能已經釋放,而會話2更新也是一瞬間的事情,所以將獲取不到其他的鎖,只能獲取3個數據庫的共享鎖,因為開啟了3個會話。(你當然可以在會話2中放置WAITFOR DELAY來延時,那么在會話3中觀察到X鎖,自己測試吧。)
最后會話的結果為:
TestID????? String
----------- --------------------
1?????????? A
?
(1 row(s) affected)
?
TestID????? String
----------- --------------------
1?????????? AA
?
(1 row(s) affected)
很明顯的,出現了MSDN描述的不可重復讀的問題。編輯人員兩次讀取同一文檔,但在兩次讀取之間,作者重寫了該文檔。當編輯人員第二次讀取文檔時,文檔已更改。
如果你看了前面我推薦的關于鎖的文章,數據庫引擎中的隔離級別這個的話,那么你應該知道“已提交讀”READ COMMITTED是不能防止這種問題的,為什么呢?請問大屏幕。
已提交讀的原理是,在有更新的資源上要去獲取S共享鎖。但是,這是的情況是,先進行查詢,資源獲取共享鎖以后,立即釋放,然后這個時候會話2更新資源,當會話一繼續的時候,那么讀取的就是更新后的數據了。也就是說,防止臟讀的隔離級別,只能保證數據在取的那一瞬間,是保證是其他事務已經提交的數據,否則發生鎖等待,等待其他事務/會話釋放X鎖,然后交給當前事務獲取S共享鎖,所以在一瞬間中,不能保證整個事務讀取的東西都不不受其他事務影響,已提交讀,只保證自己獲取的數據是別人已經提交的了。
????? 那么下面,我通過申明“可重復讀“來防止這個問題:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
在會話一設置以后,運行會話一,立即運行會話3觀察下獲取的鎖,然后運行會話2,在運行行會話3。
會話3分別顯示的結果是:
第一次:
PAGE?????? IS
OBJECT???? IS
KEY???????? S
?
第二次:
PAGE??? IX
PAGE??? IS
OBJECT IX
OBJECT IS
KEY???? S
KEY???? X
第一次結果顯示說明了,當我們指定了這個隔離級別的時候,將在所指定的資源中獲取共享鎖,而且會一直持續到事務接觸,那么就意味著,我們的會話二肯定會受到阻塞,因為他要獲取資源的排他鎖??梢詮牡?次的結果中看出來,那么也就解決了當編輯人員1處理文檔的過程中一致都不會受其他會話的影響,使多次讀取的結果不一致。
我們再來看看MSDN對這個隔離級別的詳細定義: 指定語句不能讀取已由其他事務修改但尚未提交的行,并且指定,其他任何事務都不能在當前事務完成之前修改由當前事務讀取的數據。對事務中的每個語句所讀取的全部數據都設置了共享鎖,并且該共享鎖一直保持到事務完成為止。這樣可以防止其他事務修改當前事務讀取的任何行。其他事務可以插入與當前事務所發出語句的搜索條件相匹配的新行。如果當前事務隨后重試執行該語句,它會檢索新行,從而產生幻讀。由于共享鎖一直保持到事務結束,而不是在每個語句結束時釋放,所以并發級別低于默認的 READ COMMITTED 隔離級別。此選項只在必要時使用。
紅色部分也就證明我們剛才說是放置讀取資源的共享鎖一直到事務結束的猜測是正確的。同時他提到了,這種方式不能防止幻讀。那么我們就來研究一下這個東西。
當對某行執行插入或刪除操作,而該行屬于某個事務正在讀取的行的范圍時,會發生幻讀問題。由于其他事務的刪除操作,事務第一次讀取的行的范圍顯示有一行不再存在于第二次或后續讀取內容中。同樣,由于其他事務的插入操作,事務第二次或后續讀取的內容顯示有一行并不存在于原始讀取內容中。
l 幻讀(Example 3)
例如,一個編輯人員更改作者提交的文檔,但當生產部門將其更改內容合并到該文檔的主副本時,發現作者已將未編輯的新材料添加到該文檔中。與不可重復讀的情況相似,如果在編輯人員和生產部門完成對原始文檔的處理之前,任何人都不能將新材料添加到文檔中,則可以避免此問題。
我們還是用3個會話來模擬這種環境:
會話一:
/*
?
Author:浪客
DateTime:2009.06.15 20:13
Location:成都
Description:幻讀測試會話一
?
*/
?
--先刪除ID為的這條記錄
--DELETE FROM dbo.Test1 WHERE TestID=5
?
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRAN
?
SELECT * FROM dbo.Test1 WHERE TestID<=7
WAITFOR DELAY '00:00:05'
?
COMMIT
會話2:
/*
?
Author:浪客
DateTime:2009.06.15 20:13
Location:成都
Description:幻讀測試會話二
?
*/
?
SET IDENTITY_INSERT dbo.Test1 ON
INSERT INTO dbo.Test1(TestID,String) VALUES(5,'E');
SET IDENTITY_INSERT dbo.Test1 OFF
會話3:
/*
?
Author:浪客
DateTime:2009.06.15 20:13
Location:成都
Description:幻讀測試會話三
?
*/
?
USE GoodGoodStudyDayDayUp;
GO
?
SELECT * FROM sys.dm_tran_locks
我們分別運行會話1,會話3,會話2,會話3。那么3最終的結果為:
PAGE??? IX
PAGE??? IS
KEY???? RangeS-S
?
KEY RangeI-N
OBJECT IX
OBJECT IS
KEY???? RangeS-S
KEY???? RangeS-S
KEY???? RangeS-S
KEY???? RangeS-S
KEY???? RangeS-S
KEY???? RangeS-S
KEY???? RangeS-S
我們看到結果中存在一個RangeI-N鎖,我們來看下MSDN的定義:
RangeI
Null
RangeI-N
插入范圍,空資源鎖;用于在索引中插入新鍵之前測試范圍。
再看下我們的測試中,其實有一條記錄是不存在的,所以也會有一個這樣的鎖。關于范圍鎖定看看這里
?
好了,幾種情況也和大家描述了,具體的情況還是要讀者們自己去研究,畢竟要全部說清楚還是要自己去領悟對吧。
暫時就說到這里,其他的以后想寫文章了幫大家總結,其實所有東西MSDN講述的已經夠清楚了,只不過有點凌亂,呵呵:)。
這個文章是草稿,是給公司做培訓用的,修改好的不能發哈,不好意思了
/Files/bhtfg538/T-SQL.rar
轉載于:https://www.cnblogs.com/yinyao/archive/2009/12/08/1619505.html
總結
- 上一篇: E-R图知识要点
- 下一篇: 第九章 关联数组/哈希表