十九、SqlServer鎖

一、SqlServer概述

SQL Server 數據庫支持多個用戶同時訪問數據庫,但當用戶同時訪問數據庫時,就會造成併發問題,鎖的機制能很好地解決這個問題,保證數據的完整性和一致性;
SQL Server 自帶鎖機制,若是簡單的數據庫訪問機制,完全能滿足用戶的需求;但對於數據完全與數據完整性有特殊要求,就必須自動控制鎖機制解決;

二、什麼是SQL Server 鎖機制

鎖是處理 SQL Server 中併發問題的最有效手段,當多個事務訪問同一數據時,能很好地保證數據的完整性和一致性;
在很多數據庫系統中(如DB2、MySQL、Oracle)都有鎖機制,其規則也大同小異;
在 SQL Server 中採用系統來管理鎖,SQL Server 中採用的是動態加鎖的機制;
SQL Server 中有一套默認的鎖機制,若用戶在使用數據庫的過程中不設置任何鎖,系統將自動對鎖管理;

三、SQL Server鎖模式

在 SQL Server 中有不同的鎖,在各種鎖的類型中有些是能相互兼容的,鎖的類型決定了併發
發生時數據資源的範文模式,在 SQL Server 中常用的鎖以下 5 種;

3.1、共享鎖(Shared lock)

又稱讀鎖(S鎖),共享鎖不阻塞其他事務的讀操作,但阻塞寫操作,同一數據對象A可以共存多個共享
鎖,這被稱爲共享鎖兼容。
 
當Session1爲數據對象A加上共享鎖後,可以對A進行讀操作,但不能進行寫操作,並且Session2可以
再次對A加共享鎖,大家都可以正常地讀A,但是在A上的共享鎖釋放之前,任何事務不可以對A進行寫
操作。

例 1:

Session1: select * from [ZhaoxiEdu].[dbo].[StudentsScore]
Session2: update [ZhaoxiEdu].[dbo].[StudentsScore] set Python=80
理解:
假設Session1先執行,則Session2必須等待Session1執行完纔可以執行。
因爲Session2爲寫操作,需要爲table加一個排他鎖,而數據庫規定相同資源不可以同時存在共享鎖和
排他鎖,所以Session2必須等待Session1執行完,釋放掉共享鎖,纔可以加排他鎖,然後執行
update。

例 2: (可能發生死鎖)

Session1:
begin Transaction t1
select * from [ZhaoxiEdu].[dbo].[StudentsScore] with (holdlock) -- (holdlock)共享鎖
update [ZhaoxiEdu].[dbo].[StudentsScore] set Python=80
Session2:
begin Transaction t2
select * from [ZhaoxiEdu].[dbo].[StudentsScore] with (holdlock) -- (holdlock)共享鎖
update [ZhaoxiEdu].[dbo].[StudentsScore] set Python=67
理解:
分析:
假設Session1和Session2同時到達select語句,都爲table加上了共享鎖,那麼當Session1、Session2
要執行update時,根據鎖機制,共享鎖需要升級爲排他鎖,但是排他鎖與共享鎖不能共存,要給table
加排他鎖,必須等待table上的共享鎖全部釋放纔可以,可是holdlock的共享鎖必須等待事務結束才能
釋放,因此Session1和Session2都在等待對方釋放共享鎖,形成循環等待,造成死鎖。

例3:

Session1: update [ZhaoxiEdu].[dbo].[StudentsScore] set Python=60 where Id=4
Session2: update [ZhaoxiEdu].[dbo].[StudentsScore] set Python=65 where Id=4
理解:
此種情況有可能造成等待,分爲id列有索引與無索引兩種情況。
(1)id列有索引,則Session1直接定位到Id=4行,加排他鎖,更新;Session2直接定位到Id=4行,
加排他鎖,更新。互不影響。
(2)id列無索引,Session1掃描全表,找到Id=4行,加排他鎖後,Session2爲了找到Id=4行,需要
全表掃描,那麼就會爲table加共享鎖或更新鎖或排他鎖,但不管加什麼鎖,都需要等待Session1釋放
Id=4行的排他鎖,不然無法爲全表加鎖。
死鎖可以通過直接對錶加排他鎖來解決,即將事務的隔離級別提高至最高級——串行讀,各個事務串行
執行,可是這樣雖然避免了死鎖,但是效率太低了。

3.2、更新鎖(Update lock)

更新鎖(U鎖)。當Session1給資源A加上更新鎖後,代表該資源將在稍後更新,更新鎖與共享鎖兼
容,更新鎖可以防止例2裏那種一般情況的死鎖發生,更新鎖會阻塞其他的更新鎖和排他鎖。因此相同
資源上不能存在多個更新鎖。
更新鎖允許其他事務在更新之前讀取資源。
但不可以修改。因爲其他事務想獲取資源的排他鎖時,發現該資源已存在U鎖,則等待U鎖釋放。
在Session1找到需要更新的數據時,更新鎖直接轉爲排他鎖,開始更新數據,不需要等待其他事務釋放
共享鎖啥的。
那麼就問了,共享鎖爲什麼不可以直接升級爲排他鎖,而必須等待其他共享鎖都釋放掉纔可以轉爲排他
鎖呢?
這就是共享鎖和更新鎖的一個區別了,共享鎖之間是兼容的,但是更新鎖之間互不兼容,因此僅有一個
更新鎖直接轉爲排他鎖是安全的,而多個共享鎖問也不問直接轉爲排他鎖,那怎麼行呢,排他鎖只能有
一個的,這就是爲什麼共享鎖需要等待其他共享鎖釋放纔可以升級爲排他鎖的原因了。
案例分析
例 4:
Session1:
begin
select * from [ZhaoxiEdu].[dbo].[StudentsScore] with (updlock) ---(加更新鎖)
update [ZhaoxiEdu].[dbo].[StudentsScore] set C#=70 --(重點:這裏Session1做update時,不需要等
Session2釋放什麼,而是直接把更新鎖升級爲排他鎖,然後執行update)
Session2:
begin select * from [ZhaoxiEdu].[dbo].[StudentsScore] --(T1的更新鎖不影響T2的select) update [ZhaoxiEdu].[dbo].[StudentsScore] set C#=75 --(T2的update需要等待T1的update執行完)
理解:
(1)Session1先到達,Session1的select句對table加更新鎖,此時Session2緊接着到達,Session2
的select句對table加共享鎖,假設Session2的select先執行完,要開始Session2的update,發現table
已有更新鎖,則Session2等,Session1此時執行完select,然後將更新鎖升級爲排他鎖,開始更新數
據,執行完成,事務結束,釋放排他鎖,此時Session2纔開始對table加排他鎖並更新。
(2)Session2先到,Session1緊接着,Session2加共享鎖 => Session1加更新鎖 => 假設Session2
先結束select => 試圖將共享鎖升級爲排他鎖 => 發現已有更新鎖 => 之後的情況相同;

3.3、排他鎖

又叫獨佔鎖,寫鎖,X鎖,很容易理解,排他鎖阻塞任何鎖,假設Sesssion1爲資源A加上排他鎖,則其
他事務不允許對資源A進行任何的讀寫操作。
案例分析

例 5:(假設id都是自增長且連續的)

Session1: update [ZhaoxiEdu].[dbo].[StudentsScore] set C#=70 where id<5
Session2: update [ZhaoxiEdu].[dbo].[StudentsScore] set C#=75 where id>5
理解:
假設Session1先達,Session2隨後至,這個過程中Session1會對id<5的記錄施加排他鎖.但不會阻塞
Session2的update。

例6

Session1: update [ZhaoxiEdu].[dbo].[StudentsScore] set C#=60 where id<5
Session2: update [ZhaoxiEdu].[dbo].[StudentsScore] set C#=60 where id>2
理解:
假設Session1先達,Session2立刻也到,Session1加的排他鎖會阻塞Session2的update。

3.4、意向鎖

意向鎖,就是說當你給數據加鎖時,必須先給他的上級加鎖,用來向其他事務表明這段數據中的某些數
據正在被加某某鎖,你看着辦吧。其實是一個節省開銷的做法。

例7

Session1:
begin tran
select * from [ZhaoxiEdu].[dbo].[StudentsScore] with (xlock) where id=5
--意思是對id=5這一行強加排他鎖
Session2:
begin tran
select * from [ZhaoxiEdu].[dbo].[StudentsScore] with (tablock) --意思是要加表級鎖
理解:
假設Session1先執行,Session2後執行,Session2執行時,欲加表鎖,爲判斷是否可以加表鎖,數據庫系統要逐條判斷table表每行記錄是否已有排他鎖,如果發現其中一行已經有排他鎖了,就不允許再加表鎖了。只是這樣逐條判斷效率太低了。
 
實際上,數據庫系統不是這樣工作的。當Session1的select執行時,系統對錶table的id=5的這一行加了排他鎖,還同時悄悄的對整個表加了意向排他鎖(IX),當Session2執行表鎖時,只需要看到這個表已經有意向排他鎖存在,就直接等待,而不需要逐條檢查資源了。
 
常用的意向鎖有三種:意向共享鎖(Intent Share Lock,簡稱IS鎖);意向排他鎖(Intent Exclusive Lock,簡稱IX鎖);共享意向排它鎖(Share Intent Exclusive Lock,簡稱SIX鎖),共享意向排它鎖的意思是,某事務要讀取整個表,並更新其中的某些數據。

四、鎖的粒度

SQL Server 數據庫引擎具有多粒度鎖定,允許一個事務鎖定不同類型的資源;
爲了減少鎖定的開銷,數據庫引擎自動將資源鎖定在適合任務的級別;鎖定在較小的粒度(如行)能提高併發度,但開銷較高,因爲若鎖定了許多行,就需要持有更多的鎖;鎖定在加大的粒度(如表)會降低併發,因爲鎖定整個表限制了其他事務對錶中任意部分的訪問;但其開銷較低,因爲需要維護的鎖較少; 數據庫引擎通常必須獲取多粒度級別上才能完整地保護資源,多粒度級別上的所稱爲層次結構;

五、查看鎖

在 SQL Server數據庫中,能通過查看 sys.dm_tran_locks 返回 SQL Server 數據庫中有關當前活動的
鎖的管理的信息;向鎖管理器發出的已授予鎖或正等待授予鎖的每個當前活動請求分別對應一行;結果
集中的列大體分爲兩組:資源組和請求組;
select * from sys.dm_tran_locks

六、死鎖

在兩個或多個任務中,若每一個任務都鎖定了其他的資源,就會造成永久的阻塞,這種情況就是死鎖;

形成死鎖有以下 4 個必要條件:

1. 互斥條件:資源不能被共享,只能被一個進程使用;
2. 請求與保持條件:已獲得資源的進程能同時申請其他資源;
3. 非剝奪條件:已分配的資源不能從該進程中被剝奪;
4. 循環等待條件:多個進程構成環路,且每個進程都在等待相鄰進程正在使用的資源;
 
在一個複雜的數據庫系統中很難百分之百地避免死鎖,但能按照以下的訪問策略減少死鎖的發生;
1. 所有事務中以相同的次序使用資源;避免出現循環;
2. 減少事務持有資源的時間,避免事務中的用戶交互;
3. 讓事務保持在一個批處理中;
4. 由於鎖的隔離級別越高共享鎖的時間就越長,因此能降低隔離級別來達到減少競爭的目的;
 
注意: SQL Server 數據庫引擎自動檢測 SQL Server 中的死鎖循環;數據庫引擎選擇一個會話作爲死鎖的犧牲品,然後終止當前事務(出現錯誤)來打斷死鎖。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章