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

SQL Server創建事務——鎖

enc 隔離級別 查看 serve microsoft 自動啟用 repeat ron 參考

學習地址: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 @errorNo=@errorNo+@@ERROR
 7     select ‘S_StuNo=003 已經修改啦‘
 8 
 9     update Student set C_S_Id=‘3‘ where S_StuNo=‘002‘ 
10     set @errorNo=@errorNo+@@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 @errorNo=@errorNo+@@ERROR
 7     select ‘S_StuNo=003 已經修改啦‘
 8 
 9     update Student set C_S_Id=‘5‘ where S_StuNo=‘002‘ 
10     set @errorNo=@errorNo+@@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 @errorNo=@errorNo+@@ERROR
 9     select ‘S_StuNo=003 已經修改啦‘
10 
11     update Student set C_S_Id=‘3‘ where S_StuNo=‘002‘ 
12     set @errorNo=@errorNo+@@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 @errorNo=@errorNo+@@ERROR
 9     select ‘S_StuNo=003 已經修改啦‘
10 
11     update Student set C_S_Id=‘3‘ where S_StuNo=‘002‘ 
12     set @errorNo=@errorNo+@@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

SQL Server創建事務——鎖