1. 程式人生 > >SQL Server建立事務——鎖

SQL Server建立事務——鎖

學習地址:http://www.cnblogs.com/knowledgesea/p/3714417.html

事務定義:

事務是作為單個邏輯單元執行的一系列操作,它是一個不可分割的工作邏輯單元。它包含了一組資料庫操作命令,這組命令要麼全部執行,要麼全部不執行。

舉個例子,我們經常用到的 ATM 存取款機,比如轉賬的時候,是先減去轉出賬戶的金額,然後再在指定轉入賬戶的金額加上轉出的金額。如果剛好這個時候轉出的操作已經執行完成,但是由於系統的故障,導致轉入的操作失敗了。那麼怎麼辦?這就需要用到事務了,只要事務裡面有一條命令未成功執行,那麼資料就會回滾到事務開始之前的狀態。

事務特性:

1、原子性(Atomicity):事務是一個完整的操作, 事務中所有操作命令必須作為一個整體提交或回滾。如果事務中任何操作命令失敗,則整個事務將因失敗而回滾。

2、一致性(Consistency):當事務完成時,資料都處於一致狀態。

3、隔離性(Isolation): 對資料進行修改的所有併發事務是彼此隔離的,它不以任何方式依賴或影響其他事務。

4、永續性(Durability):事務提交之後,資料是永久性的,不可再回滾。

事務操作:

1、begin transaction:開始事務。

2、commit transaction:提交事務。

3、rollback transaction:回滾事務。

4、save transaction:事務儲存點。即事務回滾時,可以指定回滾到儲存點,而不進行全部回滾。

事務分類:

1、顯式事務:用 begin transaction 明確指定事務的開始,由 commit transaction 提交事務、rollback transaction 回滾事務到事務結束。

2、隱式事務:通過設定 set implicit_transactions on 語句,將隱式事務模式設定為開啟。當以隱式事務模式操作時,不必使用 begin transaction 開啟事務,當一個事務結束後,這個模式會自動啟用下一個事務,只需使用 commit transaction 提交事務或 Rollback Transaction 回滾事務即可。

3、自動提交事務: 這是 SQL Server 的預設模式,它將每條單獨的 T-SQL 語句視為一個事務。如果成功執行,則自動提交。如果錯誤,則自動回滾。

 

先看一下資料:

複製程式碼
 1 begin tran        -- 開啟事務,transcation 的簡寫
 2 declare @errorNo int    --定義變數,用於記錄事務執行過程中的錯誤次數
 3 set @errorNo=0
 4 begin try
 5     update Student set C_S_Id='2' where S_StuNo='003'
 6     set @
[email protected]
[email protected]@ERROR 7 select 'S_StuNo=003 已經修改啦' 8 9 update Student set C_S_Id='3' where S_StuNo='002' 10 set @[email protected][email protected]@ERROR -- @@ERROR 系統全域性變數,記錄錯誤次數,出現一次錯誤 @@ERROR 值+1 11 select 'S_StuNo=002 已經修改啦' 12 13 if(@errorNo>0) 14 begin 15 --丟擲自定義的異常,在最後的catch塊中統一處理異常 16 RAISERROR(233333,16,3) 17 end 18 19 end try 20 begin catch 21 select ERROR_NUMBER() errorNumber, --錯誤程式碼 22 ERROR_SEVERITY() errorSeverity, --錯誤嚴重級別,級別小於10 try catch 捕獲不到 23 ERROR_STATE() errorState, --錯誤狀態碼 24 ERROR_PROCEDURE() errorProcedure, --出現錯誤的儲存過程或觸發器的名稱 25 ERROR_LINE() errorLine, --發生錯誤的行號 26 ERROR_MESSAGE() errorMessage --錯誤的具體資訊 27 28 if(@@trancount>0) -- @@trancount 系統全域性變數,事務開啟 @@trancount 值+1,判斷事務是否開啟 29 begin 30 rollback tran; -- 回滾事務 31 end 32 end catch 33 34 if(@@trancount>0) 35 begin 36 commit tran; -- 提交事務 37 end 38 39 select * from Student
複製程式碼

這裡由於外來鍵約束的原因,所以第二條 update 語句導致失敗,以上結果可以看出第一條資料肯定是執行過了,但是在 catch 語句裡面回滾了,所以資料還是原來的狀態。

現在只需要把第二條 update 語句 C_S_Id 列的值改為 5 即可。

複製程式碼
 1 begin tran        -- 開啟事務,transcation 的簡寫
 2 declare @errorNo int    --定義變數,用於記錄事務執行過程中的錯誤次數
 3 set @errorNo=0
 4 begin try
 5     update Student set C_S_Id='2' where S_StuNo='003'
 6     set @[email protected][email protected]@ERROR
 7     select 'S_StuNo=003 已經修改啦'
 8 
 9     update Student set C_S_Id='5' where S_StuNo='002' 
10     set @[email protected][email protected]@ERROR            -- @@ERROR 系統全域性變數,記錄錯誤次數,出現一次錯誤 @@ERROR 值+1
11     select 'S_StuNo=002 已經修改啦'
12 
13     if(@errorNo>0)
14     begin
15         --丟擲自定義的異常,在最後的catch塊中統一處理異常
16         RAISERROR(233333,16,3)
17     end
18 
19 end try
20 begin catch
21     select ERROR_NUMBER() errorNumber,        --錯誤程式碼
22            ERROR_SEVERITY() errorSeverity,    --錯誤嚴重級別,級別小於10 try catch 捕獲不到
23            ERROR_STATE() errorState,        --錯誤狀態碼
24            ERROR_PROCEDURE() errorProcedure,    --出現錯誤的儲存過程或觸發器的名稱
25            ERROR_LINE() errorLine,        --發生錯誤的行號
26            ERROR_MESSAGE() errorMessage        --錯誤的具體資訊
27 
28     if(@@trancount>0)    -- @@trancount 系統全域性變數,事務開啟 @@trancount 值+1,判斷事務是否開啟
29     begin
30         rollback tran;        -- 回滾事務
31     end
32 end catch
33 
34 if(@@trancount>0)
35 begin
36     commit tran;        -- 提交事務
37 end
38 
39 select * from Student
複製程式碼

關於 RAISERROR 自定義丟擲異常可以看這裡:http://www.cnblogs.com/Brambling/p/6687068.html

 

設定 xact_abort:

設定 xact_abort on/off , 指定是否回滾當前事務,為 on 時如果當前 sql 出錯,回滾整個事務,為 off 時如果 sql 出錯回滾當前 sql 語句,其它語句照常執行讀寫資料庫。

xact_abort 只對執行時出現的錯誤有用。

複製程式碼
 1 set xact_abort off
 2 
 3 begin tran        -- 開啟事務,transcation 的簡寫
 4 declare @errorNo int    --定義變數,用於記錄事務執行過程中的錯誤次數
 5 set @errorNo=0
 6 begin try
 7     update Student set C_S_Id='2' where S_StuNo='003'
 8     set @[email protected][email protected]@ERROR
 9     select 'S_StuNo=003 已經修改啦'
10 
11     update Student set C_S_Id='3' where S_StuNo='002' 
12     set @[email protected][email protected]@ERROR            -- @@ERROR 系統全域性變數,記錄錯誤次數,出現一次錯誤 @@ERROR 值+1
13     select 'S_StuNo=002 已經修改啦'
14 
15     if(@errorNo>0)
16     begin
17         --丟擲自定義的異常,在最後的catch塊中統一處理異常
18         RAISERROR(233333,16,3)
19     end
20 
21 end try
22 begin catch
23     select ERROR_NUMBER() errorNumber,        --錯誤程式碼
24            ERROR_SEVERITY() errorSeverity,    --錯誤嚴重級別,級別小於10 try catch 捕獲不到
25            ERROR_STATE() errorState,        --錯誤狀態碼
26            ERROR_PROCEDURE() errorProcedure,    --出現錯誤的儲存過程或觸發器的名稱
27            ERROR_LINE() errorLine,        --發生錯誤的行號
28            ERROR_MESSAGE() errorMessage        --錯誤的具體資訊
29 
30     if(@@trancount>0)    -- @@trancount 系統全域性變數,事務開啟 @@trancount 值+1,判斷事務是否開啟
31     begin
32         rollback tran;        -- 回滾事務
33     end
34 end catch
35 
36 select * from Student
複製程式碼

xact_abort 設定為 off 時,雖然也出現了異常,但是可以看出第一天資料還是修改了,並沒有回滾。因為它只是回滾出錯的 sql 語句,並不全部回滾。

複製程式碼
 1 set xact_abort on
 2 
 3 begin tran        -- 開啟事務,transcation 的簡寫
 4 declare @errorNo int    --定義變數,用於記錄事務執行過程中的錯誤次數
 5 set @errorNo=0
 6 begin try
 7     update Student set C_S_Id='2' where S_StuNo='003'
 8     set @[email protected][email protected]@ERROR
 9     select 'S_StuNo=003 已經修改啦'
10 
11     update Student set C_S_Id='3' where S_StuNo='002' 
12     set @[email protected][email protected]@ERROR            -- @@ERROR 系統全域性變數,記錄錯誤次數,出現一次錯誤 @@ERROR 值+1
13     select 'S_StuNo=002 已經修改啦'
14 
15     if(@errorNo>0)
16     begin
17         --丟擲自定義的異常,在最後的catch塊中統一處理異常
18         RAISERROR(233333,16,3)
19     end
20 
21 end try
22 begin catch
23     select ERROR_NUMBER() errorNumber,        --錯誤程式碼
24            ERROR_SEVERITY() errorSeverity,    --錯誤嚴重級別,級別小於10 try catch 捕獲不到
25            ERROR_STATE() errorState,        --錯誤狀態碼
26            ERROR_PROCEDURE() errorProcedure,    --出現錯誤的儲存過程或觸發器的名稱
27            ERROR_LINE() errorLine,        --發生錯誤的行號
28            ERROR_MESSAGE() errorMessage        --錯誤的具體資訊
29 
30     if(@@trancount>0)    -- @@trancount 系統全域性變數,事務開啟 @@trancount 值+1,判斷事務是否開啟
31     begin
32         rollback tran;        -- 回滾事務
33     end
34 end catch
35 
36 select * from Student
複製程式碼

xact_abort 設定為 on 時,出現了異常,回滾整個事務。

 

事務死鎖:

開啟兩個查詢視窗,把下面的語句,分別放入2個查詢視窗,在5秒內執行2個事務模組。

複製程式碼
1 begin tran 
2   update Student set C_S_Id='2' where S_StuNo='002'
3 
4   waitfor delay '0:0:5'
5 
6   update Student set C_S_Id='5' where S_StuNo='003'
7 commit tran
8 
9 select * from Student
複製程式碼 複製程式碼
1 begin tran 
2   update Student set C_S_Id='5' where S_StuNo='003'
3 
4   waitfor delay '0:0:5'
5 
6   update Student set C_S_Id='2' where S_StuNo='002'
7 commit tran
8 
9 select * from Student
複製程式碼

因為事務在執行過程中會將事務中用到的表和資料進行鎖定,直到事務結束(提交或回滾),才會釋放。

在很多使用者都同時使用事務訪問同一個資料資源的情況下,就會造成以下幾種資料錯誤:

1、更新丟失:多個使用者同時對一個數據資源進行更新,必定會產生被覆蓋的資料,造成資料讀寫異常。

2、不可重複讀:如果一個使用者在一個事務中多次讀取一條資料,而另外一個使用者則同時更新啦這條資料,造成第一個使用者多次讀取資料不一致。

3、髒讀:第一個事務讀取第二個事務正在更新的資料表,如果第二個事務還沒有更新完成,那麼第一個事務讀取的資料將是一半為更新過的,一半還沒更新過的資料。

4、幻讀:第一個事務讀取一個結果集後,第二個事務,對這個結果集進行增刪改操作,然而第一個事務中再次對這個結果集進行查詢時,資料發現丟失或新增。

然而鎖定,就是為解決這些問題的,它的存在使得一個事務對它自己的資料塊進行操作的時候,而另外一個事務則不能插足這些資料塊。這就是所謂的鎖定。

 

鎖相容性具體參見:http://msdn.microsoft.com/zh-cn/library/ms186396.aspx

鎖粒度和層次結構參見:http://msdn.microsoft.com/zh-cn/library/ms189849(v=sql.105).aspx

 

什麼是死鎖,為什麼會產生死鎖。見上面的例子。

例子是這樣的:

第一個事務(稱為A):先更新表 Student S_StuNo='003' 這條資料 --->>停頓5秒---->>更新表 Student S_StuNo='002' 這條資料

第二個事務(稱為B):先更新表 Student S_StuNo='002' 這條資料--->>停頓5秒---->>更新表 Student S_StuNo='003' 這條資料

先執行事務A----5秒之內---執行事務B,出現死鎖現象。

過程是這樣子的:

  1. A更新表 Student S_StuNo='003' 這條資料,請求排他鎖,成功。
  2. B更新表 Student S_StuNo='002' 這條資料,請求排他鎖,成功。
  3. 5秒過後
  4. A更新表 Student S_StuNo='002' 這條資料,請求排它鎖,由於B佔用著表 Student S_StuNo='002' 這條資料,等待。
  5. B更新表 Student S_StuNo='003' 這條資料,請求排它鎖,由於A佔用著表 Student S_StuNo='003' 這條資料,等待。

這樣相互等待對方釋放資源,造成資源讀寫擁擠堵塞的情況,就被稱為死鎖現象,也叫做阻塞。而為什麼會產生,上例就列舉出來啦。

然而資料庫並沒有出現無限等待的情況,是因為資料庫搜尋引擎會定期檢測這種狀況,一旦發現有情況,立馬選擇一個事務作為犧牲品。犧牲的事務,將會回滾資料。

但是我們可以指定具體哪個事務作為犧牲品:

語法:

1 set deadlock_priority  <級別>

死鎖處理的優先級別為 low < normal < high,不指定的情況下預設為normal,犧牲品為隨機。如果指定,犧牲品為級別低的。

還可以使用數字來處理標識級別:-10 到 -5 為 low,-5 為 normal,-5 到 10 為 high。

 

死鎖耗時耗資源,然而在大型資料庫中,高併發帶來的死鎖是不可避免的,儘管死鎖不能完全避免,但遵守特定的編碼慣例可以將發生死鎖的機會降至最低。將死鎖減至最少可以增加事務的吞吐量並減少系統開銷,因為只有很少的事務:

  • 回滾,撤消事務執行的所有工作。

  • 由於死鎖時回滾而由應用程式重新提交。

下列方法有助於將死鎖減至最少:

1、按同一順序訪問資料庫物件資源。

2、避免事務中的使用者互動,即事務中等待使用者輸入、提交等操作。

3、保持事務簡短並處於一個批處理中,在同一資料庫中併發執行多個需要長時間執行的事務時通常會發生死鎖。事務的執行時間越長,它持有排他鎖或更新鎖的時間也就越長,從而會阻塞其他活動並可能導致死鎖。

4、使用較低的隔離級別,確定事務是否能在較低的隔離級別上執行。實現已提交讀允許事務讀取另一個事務已讀取(未修改)的資料,而不必等待第一個事務完成。使用較低的隔離級別(例如已提交讀)比使用較高的隔離級別(例如可序列化)持有共享鎖的時間更短。這樣就減少了鎖爭用。

5、儘可能使用分割槽表,分割槽檢視,把資料放置在不同的磁碟和檔案組中,分散訪問儲存在不同分割槽的資料,減少因為表中放置鎖而造成的其它事務長時間等待。

 

可參考:http://msdn.microsoft.com/zh-cn/library/ms191242(v=sql.105).aspx

 

檢視鎖和事務活動情況:

1 --檢視鎖活動情況
2 select * from sys.dm_tran_locks
3 --檢視事務活動情況
4 dbcc opentran

可參考:http://msdn.microsoft.com/zh-cn/library/ms190345.aspx

 

事務隔離級別:

事物隔離級別,分為5種。就是併發事務對同一資源的讀取深度層次。

1、read uncommitted:這個隔離級別最低,可以讀取到一個事務正在處理的資料,但事務還未提交,這種級別的讀取叫做髒讀。

2、read committed:這個級別是預設選項,不能髒讀,不能讀取事務正在處理沒有提交的資料,但能修改。

3、repeatable read:不能讀取事務正在處理的資料,也不能修改事務處理資料前的資料。

4、snapshot:指定事務在開始的時候,就獲得了已經提交資料的快照,因此當前事務只能看到事務開始之前對資料所做的修改。

5、serializable:最高事務隔離級別,只能看到事務處理之前的資料。 

語法:

1 -- 設定事務隔離級別
2 set tran isolation level <級別>

read uncommitted 隔離級別的例子:

複製程式碼
1 begin tran 
2   set deadlock_priority low        -- 設定死鎖處理的優先級別為 low
3 
4   update Student set C_S_Id='2' where S_StuNo='002'
5 
6   waitfor delay '0:0:5'        -- 等待5秒執行下面的回滾事務
7 rollback tran
8 
9 select * from Student
複製程式碼

5秒內在另外一個查詢視窗執行下面語句:

複製程式碼
1 set tran isolation level read uncommitted        -- 設定事務隔離級別為 read uncommitted 
2 
3   select * from Student        -- 讀取的資料為正在修改的資料,髒讀
4 
5   waitfor delay '0:0:5'        -- 5秒之後資料已經回滾
6 
7 select * from Student        -- 回滾之後的資料
複製程式碼

read committed 隔離級別的例子:

複製程式碼
1 begin tran 
2   set deadlock_priority low        -- 設定死鎖處理的優先級別為 low
3 
4   update Student set C_S_Id='2' where S_StuNo='002'        -- 修改為 2
5 
6   waitfor delay '0:0:5'        -- 等待5秒執行下面的回滾事務
7 rollback tran
8 
9 select * from Student
複製程式碼 複製程式碼
1 set tran isolation level read committed        -- 設定事務隔離級別為 read committed 
2 
3   select * from Student        -- 讀取不到正在修改的資料,不能髒讀
4 
5   update Student set C_S_Id='5' where S_StuNo='002'        -- 修改為 5
6 
7   waitfor delay '0:0:5'        -- 5秒之後上一個事務已經回滾
8 
9 select * from Student        -- 修改之後的資料
複製程式碼

 

設定鎖超時時間:

發生死鎖的時候,資料庫引擎會自動檢測死鎖,解決問題,然而這樣子是很被動,只能在發生死鎖後,等待處理。

然而我們也可以主動出擊,設定鎖超時時間,一旦資源被鎖定阻塞,超過設定的鎖定時間,阻塞語句自動取消,釋放資源,報1222錯誤。

任何事情都具有兩面性,調優的同時,也有他的不足之處,那就是一旦超過時間,語句取消,釋放資源,但是當前報錯事務,不會回滾,會造成資料錯誤,你需要在程式中捕獲1222錯誤,用程式處理當前事務的邏輯,使資料正確。

1 --檢視鎖超時時間,預設為-1
2 select @@lock_timeout
3 
4 --設定鎖超時時間
5 set lock_timeout 0    --為0時,即為一旦發現資源鎖定,立即報錯,不再等待,當前事務不回滾,設定時間需謹慎處理

 

之前從沒接觸過事務,今天跟著學習了一下,哇!!!事務還挺好理解的,不過事務併發死鎖這類問題就需要實際經驗了。所以我理解也不是太深刻,不過大家可以看下面這篇文章,我就是跟著學習的,個人覺得寫的很不錯。

學習地址:

http://www.cnblogs.com/knowledgesea/p/3714417.html