小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

sql server鎖知識及鎖應(yīng)用


提示:這里所摘抄的關(guān)于鎖的知識有的是不同sql server版本的,對應(yīng)于特定版本時會有問題。

一 關(guān)于鎖的基礎(chǔ)知識


(一). 為什么要引入鎖

當(dāng)多個用戶同時對數(shù)據(jù)庫的并發(fā)操作時會帶來以下數(shù)據(jù)不一致的問題:

◆丟失更新

A,B兩個用戶讀同一數(shù)據(jù)并進(jìn)行修改,其中一個用戶的修改結(jié)果破壞了另一個修改的結(jié)果,比如訂票系統(tǒng)

◆臟讀

A用戶修改了數(shù)據(jù),隨后B用戶又讀出該數(shù)據(jù),但A用戶因為某些原因取消了對數(shù)據(jù)的修改,數(shù)據(jù)恢復(fù)原值,此時B得到的數(shù)據(jù)就與數(shù)據(jù)庫內(nèi)的數(shù)據(jù)產(chǎn)生了不一致

◆不可重復(fù)讀

A用戶讀取數(shù)據(jù),隨后B用戶讀出該數(shù)據(jù)并修改,此時A用戶再讀取數(shù)據(jù)時發(fā)現(xiàn)前后兩次的值不一致

并發(fā)控制的主要方法是封鎖,鎖就是在一段時間內(nèi)禁止用戶做某些操作以避免產(chǎn)生數(shù)據(jù)不一致


(二) 鎖的分類

◆鎖的類別有兩種分法:


1. 從數(shù)據(jù)庫系統(tǒng)的角度來看:分為獨占鎖(即排它鎖),共享鎖和更新鎖

MS-SQL Server 使用以下資源鎖模式。

鎖模式 描述

共享 (S) 用于不更改或不更新數(shù)據(jù)的操作(只讀操作),如 SELECT 語句。

更新 (U) 用于可更新的資源中。防止當(dāng)多個會話在讀取、鎖定以及隨后可能進(jìn)行的資源更新時發(fā)生常見形式的死鎖。

排它 (X) 用于數(shù)據(jù)修改操作,例如 INSERT、UPDATE 或 DELETE。確保不會同時同一資源進(jìn)行多重更新。

意向鎖 用于建立鎖的層次結(jié)構(gòu)。意向鎖的類型為:意向共享 (IS)、意向排它 (IX) 以及與意向排它共享 (SIX)。

架構(gòu)鎖 在執(zhí)行依賴于表架構(gòu)的操作時使用。架構(gòu)鎖的類型為:架構(gòu)修改 (Sch-M) 和架構(gòu)穩(wěn)定性 (Sch-S)。

大容量更新 (BU) 向表中大容量復(fù)制數(shù)據(jù)并指定了 TABLOCK 提示時使用。


◆共享鎖

共享 (S) 鎖允許并發(fā)事務(wù)讀取 (SELECT) 一個資源。資源上存在共享 (S) 鎖時,任何其它事務(wù)都不能修改數(shù)據(jù)。一旦已經(jīng)讀取數(shù)據(jù),便立即釋放資源上的共享 (S) 鎖,除非將事務(wù)隔離級別設(shè)置為可重復(fù)讀或更高級別,或者在事務(wù)生存周期內(nèi)用鎖定提示保留共享 (S) 鎖。


◆更新鎖

更新 (U) 鎖可以防止通常形式的死鎖。一般更新模式由一個事務(wù)組成,此事務(wù)讀取記錄,獲取資源(頁或行)的共享 (S) 鎖,然后修改行,此操作要求鎖轉(zhuǎn)換為排它 (X) 鎖。如果兩個事務(wù)獲得了資源上的共享模式鎖,然后試圖同時更新數(shù)據(jù),則一個事務(wù)嘗試將鎖轉(zhuǎn)換為排它 (X) 鎖。共享模式到排它鎖的轉(zhuǎn)換必須等待一段時間,因為一個事務(wù)的排它鎖與其它事務(wù)的共享模式鎖不兼容;發(fā)生鎖等待。第二個事務(wù)試圖獲取排它 (X) 鎖以進(jìn)行更新。由于兩個事務(wù)都要轉(zhuǎn)換為排它 (X) 鎖,并且每個事務(wù)都等待另一個事務(wù)釋放共享模式鎖,因此發(fā)生死鎖。

若要避免這種潛在的死鎖問題,請使用更新 (U) 鎖。一次只有一個事務(wù)可以獲得資源的更新 (U) 鎖。如果事務(wù)修改資源,則更新 (U) 鎖轉(zhuǎn)換為排它 (X) 鎖。否則,鎖轉(zhuǎn)換為共享鎖。


◆排它鎖

排它 (X) 鎖可以防止并發(fā)事務(wù)對資源進(jìn)行訪問。其它事務(wù)不能讀取或修改排它 (X) 鎖鎖定的數(shù)據(jù)

 

◆意向鎖

意向鎖表示 SQL Server 需要在層次結(jié)構(gòu)中的某些底層資源上獲取共享 (S) 鎖或排它 (X) 鎖。例如,放置在表級的共享意向鎖表示事務(wù)打算在表中的頁或行上放置共享 (S) 鎖。在表級設(shè)置意向鎖可防止另一個事務(wù)隨后在包含那一頁的表上獲取排它 (X) 鎖。意向鎖可以提高性能,因為 SQL Server 僅在表級檢查意向鎖來確定事務(wù)是否可以安全地獲取該表上的鎖。而無須檢查表中的每行或每頁上的鎖以確定事務(wù)是否可以鎖定整個表。


意向鎖包括意向共享 (IS)、意向排它 (IX) 以及與意向排它共享 (SIX)。


鎖模式 描述

意向共享 (IS) 通過在各資源上放置 S 鎖,表明事務(wù)的意向是讀取層次結(jié)構(gòu)中的部分(而不是全部)底層資源。

意向排它 (IX) 通過在各資源上放置 X 鎖,表明事務(wù)的意向是修改層次結(jié)構(gòu)中的部分(而不是全部)底層資源。IX 是 IS 的超集。

與意向排它共享 (SIX) 通過在各資源上放置 IX 鎖,表明事務(wù)的意向是讀取層次結(jié)構(gòu)中的全部底層資源并修改部分(而不是全部)底層資源。允許頂層資源上的并發(fā) IS 鎖。例如,表的 SIX 鎖在表上放置一個 SIX 鎖(允許并發(fā) IS 鎖),在當(dāng)前所修改頁上放置 IX 鎖(在已修改行上放置 X 鎖)。雖然每個資源在一段時間內(nèi)只能有一個 SIX 鎖,以防止其它事務(wù)對資源進(jìn)行更新,但是其它事務(wù)可以通過獲取表級的 IS 鎖來讀取層次結(jié)構(gòu)中的底層資源。


◆獨占鎖:

只允許進(jìn)行鎖定操作的程序使用,其他任何對他的操作均不會被接受。執(zhí)行數(shù)據(jù)更新命令時,SQL Server會自動使用獨占鎖。當(dāng)對象上有其他鎖存在時,無法對其加獨占鎖。

共享鎖:共享鎖鎖定的資源可以被其他用戶讀取,但其他用戶無法修改它,在執(zhí)行Select時,SQL Server會對對象加共享鎖。

◆更新鎖:

當(dāng)SQL Server準(zhǔn)備更新數(shù)據(jù)時,它首先對數(shù)據(jù)對象作更新鎖鎖定,這樣數(shù)據(jù)將不能被修改,但可以讀取。等到SQL Server確定要進(jìn)行更新數(shù)據(jù)操作時,他會自動將更新鎖換為獨占鎖,當(dāng)對象上有其他鎖存在時,無法對其加更新鎖。


2. 從程序員的角度看:分為樂觀鎖和悲觀鎖。

◆樂觀鎖:完全依靠數(shù)據(jù)庫來管理鎖的工作。

◆悲觀鎖:程序員自己管理數(shù)據(jù)或?qū)ο笊系逆i處理。


MS-SQLSERVER 使用鎖在多個同時在數(shù)據(jù)庫內(nèi)執(zhí)行修改的用戶間實現(xiàn)悲觀并發(fā)控制


三 鎖的粒度

 

鎖粒度是被封鎖目標(biāo)的大小,封鎖粒度小則并發(fā)性高,但開銷大,封鎖粒度大則并發(fā)性低但開銷小

SQL Server支持的鎖粒度可以分為為行、頁、鍵、鍵范圍、索引、表或數(shù)據(jù)庫獲取鎖

資源 描述

RID 行標(biāo)識符。用于單獨鎖定表中的一行。

鍵 索引中的行鎖。用于保護(hù)可串行事務(wù)中的鍵范圍。

頁 8 千字節(jié) (KB) 的數(shù)據(jù)頁或索引頁。

擴(kuò)展盤區(qū) 相鄰的八個數(shù)據(jù)頁或索引頁構(gòu)成的一組。

表 包括所有數(shù)據(jù)和索引在內(nèi)的整個表。

DB 數(shù)據(jù)庫。

SQL Server 提供以下的鎖級別:

DATABASE  -- 無論何時當(dāng)一個SQL Server 進(jìn)程正在使用除master以外的數(shù)據(jù)庫時,Lock Manager為該進(jìn)程授予數(shù)據(jù)庫級的鎖。數(shù)據(jù)庫級的鎖總是共享鎖,用于跟蹤何時數(shù)據(jù)庫在使用中,以防其他進(jìn)程刪除該數(shù)據(jù)庫,將數(shù)據(jù)庫置為脫機(jī),或者恢復(fù)數(shù)據(jù)庫。注意,由于master和tempdb數(shù)據(jù)庫不能被刪除或置為脫機(jī),所以不需要在它們之上加鎖。
FILE -- 文件級的鎖用于鎖定數(shù)據(jù)庫文件。
EXTENT -- Extent鎖用于鎖定extents,通常僅在空間分配和重新分配的時候使用。一個extent由8個連續(xù)的數(shù)據(jù)頁或索引頁組成。Extent鎖可以是共享鎖也可以是獨占鎖。
ALLOCATION_UNIT -- 使用在數(shù)據(jù)庫分配單元上。
TABLE -- 這種級別的鎖將鎖定整個表,包括數(shù)據(jù)和索引。何時將獲得表級鎖的例子包括在Serializable隔離級別下從包含大量數(shù)據(jù)的表中選取所有的行,以及在表上執(zhí)行不帶過濾條件的update或delete。
Heap or B-Tree (HOBT) -- 用于堆數(shù)據(jù)頁,或者索引的二叉樹結(jié)構(gòu)。
PAGE -- 使用頁級鎖,由8KB數(shù)據(jù)或者索引信息組成的整個頁被鎖定。當(dāng)需要讀取一頁的所有行或者需要執(zhí)行頁級別的維護(hù)如頁拆分后更新頁指針時,將會獲取頁級鎖。
Row ID (RID) -- 使用RID鎖,頁內(nèi)的單一行被鎖定。無論何時當(dāng)提供最大化的資源并發(fā)性訪問是有效并且可能時,將獲得RID鎖。
KEY -- SQL Server使用兩種類型的Key鎖。其中一個的使用取決于當(dāng)前會話的鎖隔離級別。對于運行于Read Committed 或者 Repeatable Read 隔離模式下的事務(wù),SQL Server 鎖定與被訪問的行相關(guān)聯(lián)的的實際索引key。(如果是表的聚集索引,數(shù)據(jù)行位于索引的葉級。行上在這些你看到的是Key鎖而不是行級鎖。)若在Serializable隔離模式下,通過鎖定一定范圍的key值從而不允許新的行插入到該范圍內(nèi),SQL Server防止了“幻讀”。這些鎖因而被稱作“key-range lock”。
METADATA -- 用于鎖定系統(tǒng)目錄信息(元數(shù)據(jù))。
APPLICATION -- 允許用戶定義他們自己的鎖,指定資源名稱、鎖模式、所有者、timeout間隔。

四 SQL Server 鎖類型(與粒度相對應(yīng))


1. HOLDLOCK: 在該表上保持共享鎖,直到整個事務(wù)結(jié)束,而不是在語句執(zhí)行完立即釋放所添加的鎖。   
2. NOLOCK:不添加共享鎖和排它鎖,當(dāng)這個選項生效后,可能讀到未提交讀的數(shù)據(jù)或“臟數(shù)據(jù)”,這個選項僅僅應(yīng)用于SELECT語句。   
3. PAGLOCK:指定添加頁鎖(否則通??赡芴砑颖礞i)?!?nbsp;
4. READCOMMITTED用與運行在提交讀隔離級別的事務(wù)相同的鎖語義執(zhí)行掃描。默認(rèn)情況下,SQL Server 2000 在此隔離級別上操作。 
5. READPAST: 跳過已經(jīng)加鎖的數(shù)據(jù)行,這個選項將使事務(wù)讀取數(shù)據(jù)時跳過那些已經(jīng)被其他事務(wù)鎖定的數(shù)據(jù)行,而不是阻塞直到其他事務(wù)釋放鎖,READPAST僅僅應(yīng)用于READ COMMITTED隔離性級別下事務(wù)操作中的SELECT語句操作?! ?br> 6. READUNCOMMITTED:等同于NOLOCK?! ?nbsp;
7. REPEATABLEREAD:設(shè)置事務(wù)為可重復(fù)讀隔離性級別?!?nbsp;
8. ROWLOCK:使用行級鎖,而不使用粒度更粗的頁級鎖和表級鎖?! ?nbsp;
9. SERIALIZABLE:用與運行在可串行讀隔離級別的事務(wù)相同的鎖語義執(zhí)行掃描。等同于 HOLDLOCK?!?nbsp;
10. TABLOCK:指定使用表級鎖,而不是使用行級或頁面級的鎖,SQL Server在該語句執(zhí)行完后釋放這個鎖,而如果同時指定了HOLDLOCK,該鎖一直保持到這個事務(wù)結(jié)束?! ?br> 11. TABLOCKX:指定在表上使用排它鎖,這個鎖可以阻止其他事務(wù)讀或更新這個表的數(shù)據(jù),直到這個語句或整個事務(wù)結(jié)束?!?nbsp;
12. UPDLOCK :指定在讀表中數(shù)據(jù)時設(shè)置更新 鎖(update lock)而不是設(shè)置共享鎖,該鎖一直保持到這個語句或整個事務(wù)結(jié)束,使用UPDLOCK的作用是允許用戶先讀取數(shù)據(jù)(而且不阻塞其他用戶讀數(shù)據(jù)),并且保證在后來再更新數(shù)據(jù)時,這一段時間內(nèi)這些數(shù)據(jù)沒有被其他用戶修改。


五 鎖定時間的長短


鎖保持的時間長度為保護(hù)所請求級別上的資源所需的時間長度。


用于保護(hù)讀取操作的共享鎖的保持時間取決于事務(wù)隔離級別。采用 READ COMMITTED 的默認(rèn)事務(wù)隔離級別時,只在讀取頁的期間內(nèi)控制共享鎖。在掃描中,直到在掃描內(nèi)的下一頁上獲取鎖時才釋放鎖。如果指定 HOLDLOCK 提示或者將事務(wù)隔離級別設(shè)置為 REPEATABLE READ 或 SERIALIZABLE,則直到事務(wù)結(jié)束才釋放鎖。


根據(jù)為游標(biāo)設(shè)置的并發(fā)選項,游標(biāo)可以獲取共享模式的滾動鎖以保護(hù)提取。當(dāng)需要滾動鎖時,直到下一次提取或關(guān)閉游標(biāo)(以先發(fā)生者為準(zhǔn))時才釋放滾動鎖。但是,如果指定 HOLDLOCK,則直到事務(wù)結(jié)束才釋放滾動鎖。


用于保護(hù)更新的排它鎖將直到事務(wù)結(jié)束才釋放。

如果一個連接試圖獲取一個鎖,而該鎖與另一個連接所控制的鎖沖突,則試圖獲取鎖的連接將一直阻塞到:

將沖突鎖釋放而且連接獲取了所請求的鎖。

連接的超時間隔已到期。默認(rèn)情況下沒有超時間隔,但是一些應(yīng)用程序設(shè)置超時間隔以防止無限期等待 


六 SQL Server 中鎖的自定義


◆處理死鎖和設(shè)置死鎖優(yōu)先級


死鎖就是多個用戶申請不同封鎖,由于申請者均擁有一部分封鎖權(quán)而又等待其他用戶擁有的部分封鎖而引起的無休止的等待


可以使用SET DEADLOCK_PRIORITY控制在發(fā)生死鎖情況時會話的反應(yīng)方式。如果兩個進(jìn)程都鎖定數(shù)據(jù),并且直到其它進(jìn)程釋放自己的鎖時,每個進(jìn)程才能釋放自己的鎖,即發(fā)生死鎖情況。


◆2 處理超時和設(shè)置鎖超時持續(xù)時間。


@@LOCK_TIMEOUT 返回當(dāng)前會話的當(dāng)前鎖超時設(shè)置,單位為毫秒


SET LOCK_TIMEOUT 設(shè)置允許應(yīng)用程序設(shè)置語句等待阻塞資源的最長時間。當(dāng)語句等待的時間大于 LOCK_TIMEOUT 設(shè)置時,系統(tǒng)將自動取消阻塞的語句,并給應(yīng)用程序返回"已超過了鎖請求超時時段"的 1222 號錯誤信息


示例

下例將鎖超時期限設(shè)置為 1,800 毫秒。

SET LOCK_TIMEOUT 1800


◆設(shè)置事務(wù)隔離級別。


◆對 SELECT、INSERT、UPDATE 和 DELETE 語句使用表級鎖定提示。


◆配置索引的鎖定粒度

可以使用 sp_indexoption 系統(tǒng)存儲過程來設(shè)置用于索引的鎖定粒度


七 查看鎖的信息


1 執(zhí)行 EXEC SP_LOCK 報告有關(guān)鎖的信息

2 查詢分析器中按Ctrl+2可以看到鎖的信息


八 使用注意事項


如何避免死鎖,最小化鎖競爭

1 使用事務(wù)時,盡量縮短事務(wù)的邏輯處理過程,及早提交或回滾事務(wù),事務(wù)持有鎖的時間越短,鎖競爭發(fā)生的機(jī)會就越少;將不是事務(wù)所管理的工作單元鎖必需的命令移出事務(wù)。;

2 設(shè)置死鎖超時參數(shù)為合理范圍,如:3分鐘-10分種;超過時間,自動放棄本次操作,避免進(jìn)程懸掛;

3 優(yōu)化程序,檢查并避免死鎖現(xiàn)象出現(xiàn);

4 .對所有的腳本和SP都要仔細(xì)測試,在正是版本之前。

5 所有的SP都要有錯誤處理(通過@error)

6 一般不要修改SQL SERVER事務(wù)的默認(rèn)級別。不推薦強(qiáng)行加鎖

7 將組成事務(wù)的語句作為一個的單獨的批命令處理,以消除 BEGIN TRAN 和 COMMIT  TRAN 語句之間的網(wǎng)絡(luò)延遲造成的不必要的延遲。

8 考慮完全地使用存儲過程編寫事務(wù)代碼。典型地,存儲過程比批命令運行更快。

9 在游標(biāo)中盡可早地Commit更新。因為游標(biāo)處理比面向集合的處理慢得多,因此導(dǎo)致鎖被持有的時間更久。

10 使用每個進(jìn)程所需的最低級別的鎖隔離。比如說,如果臟讀是可接受的并且不要求結(jié)果必須精確,那么可以考慮使用事務(wù)隔離級別0(Read Uncommitted),僅在絕對必要時才使用Repeatable Read or Serializable隔離級別。

11 在 BEGIN TRAN 和 COMMIT TRAN 語句之間,絕不允許用戶交互,因為這樣做可能鎖被持有無限期的時間。


九 幾個有關(guān)鎖的問題


1 如何鎖一個表的某一行

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

SELECT * FROM table ROWLOCK WHERE id = 1

 

2 鎖定數(shù)據(jù)庫的一個表


SELECT * FROM table WITH (HOLDLOCK)

加鎖語句:

sybase:
update 表 set col1=col1 where 1=0 ;
MSSQL:
select col1 from 表 (tablockx) where 1=0 ;
oracle:
LOCK TABLE 表 IN EXCLUSIVE MODE ;
加鎖后其它人不可操作,直到加鎖用戶解鎖,用commit或rollback解鎖


◆排它鎖

新建兩個連接,在第一個連接中執(zhí)行以下語句

begin tran 
update table1 set A='aa' where B='b2' 
waitfor delay '00:00:30' --等待30秒 
commit tran 
--在第二個連接中執(zhí)行以下語句 
begin tran 
select * from table1 where B='b2' 
commit tran
 

若同時執(zhí)行上述兩個語句,則select查詢必須等待update執(zhí)行完畢才能執(zhí)行即要等待30秒


◆共享鎖

在第一個連接中執(zhí)行以下語句

begin tran 
select * from table1 holdlock  --holdlock人為加鎖 
where B='b2' 
waitfor delay '00:00:30' --等待30秒 
commit tran

◆共享鎖

在第一個連接中執(zhí)行以下語句

begin tran 
select * from table1 holdlock --holdlock人為加鎖 
where B='b2' 
waitfor delay '00:00:30' --等待30秒 
commit tran
 

在第二個連接中執(zhí)行以下語句

begin tran 
select A,C from table1 where B='b2' 
update table1 set A='aa' where B='b2' 
commit tran

若同時執(zhí)行上述兩個語句,則第二個連接中的select查詢可以執(zhí)行

而update必須等待第一個事務(wù)釋放共享鎖轉(zhuǎn)為排它鎖后才能執(zhí)行 即要等待30秒

◆死鎖

--在第一個連接中執(zhí)行以下語句 
begin tran 
update table1  set A='aa' where B='b2' 
waitfor delay '00:00:30' 
update table2  set D='d5'  where E='e1' 
commit tran
 
--在第二個連接中執(zhí)行以下語句

begin tran 
update table2  set D='d5'  where E='e1' 
waitfor delay '00:00:10' 
update table1  set A='aa'  where B='b2' 
commit tran

同時執(zhí)行,系統(tǒng)會檢測出死鎖,并中止進(jìn)程


十 應(yīng)用程序鎖:

應(yīng)用程序鎖就是客戶端代碼生成的鎖,而不是sql server本身生成的鎖

處理應(yīng)用程序鎖的兩個過程

sp_getapplock 鎖定應(yīng)用程序資源

sp_releaseapplock 為應(yīng)用程序資源解鎖

注意: 鎖定數(shù)據(jù)庫的一個表的區(qū)別

SELECT * FROM table WITH (HOLDLOCK) 其他事務(wù)可以讀取表,但不能更新刪除

SELECT * FROM table WITH (TABLOCKX) 其他事務(wù)不能讀取表,更新和刪除

交讀事務(wù)使用行版本控制。
使用快照隔離。
使用綁定連接。


二 鎖的分析及應(yīng)用系列

1 用SqlServer Profile來查看分析鎖的信息

  這個工具我想大家都明白,它的監(jiān)視能力真的是無所不能。。。鎖的痙攣狀態(tài)也全在它的掌握之中。

1. 首先我做一個Person表,Name字段設(shè)定4000字節(jié),這樣一個數(shù)據(jù)頁可以容納2條數(shù)據(jù),如下圖:

DROP TABLE dbo.Person
CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'aaaaa')
--插入6條,生成3個數(shù)據(jù)頁
INSERT INTO dbo.Person DEFAULT VALUES
go 6


2. 下面我們看看數(shù)據(jù)在數(shù)據(jù)頁的分布情況。



3. 然后我們開啟Profile,在“事件選擇”的Events中選擇”Lock:Acquired“和”Lock:Released“ ,然后運行,如下圖:



使用測試數(shù)據(jù)

1. 首先我執(zhí)行一個簡單的 SELECT * FROM dbo.Person,看看表/數(shù)據(jù)頁/記錄的加鎖情況。


從圖中可以看到,select執(zhí)行的大概步驟如下:

第一步:給表(Object)加上IS(意向共享鎖)。

第二步:先給1:78號數(shù)據(jù)頁加IS鎖,掃描78號數(shù)據(jù)頁,然后釋放IS鎖。

第三步:同樣的道理掃描之后的數(shù)據(jù)頁。

第四步:最后釋放表的IS鎖,結(jié)束整個鎖流程。

看完上面的一系列的Lock:Acquired 和 Lock:Released的話,你有沒有發(fā)現(xiàn)一個問題,不是說好給記錄(RID)加上S鎖么???這里沒加,是因為引擎進(jìn)入78號數(shù)據(jù)頁的時候,未發(fā)現(xiàn)它存在IU鎖或者IX鎖。。。所以。。。這個屬于鎖的組合,后續(xù)會說。


2. 接下來用UPDATE dbo.Person SET NAME='bbbbb' WHERE ID=3來看看update的整個過程,乍一看,Profile捕獲到的記錄還是比較多的,下面具體看圖:


 第一步: 給表(Object)加上IX鎖,

 第二步: 給數(shù)據(jù)頁(1:78)數(shù)據(jù)頁分配IU鎖。然后開始逐一掃描78號數(shù)據(jù)頁的RID記錄,進(jìn)入前就Acquired,退出后就Released,當(dāng)掃描完78號數(shù)據(jù)頁的所有RID后,再釋放78                  號數(shù)據(jù)頁的IU鎖,進(jìn)入下一個數(shù)據(jù)頁。。。

 第三步: 我們發(fā)現(xiàn)ID=3是在89號數(shù)據(jù)頁上,當(dāng)引擎掃到該RID之后,我們觀察到89號的數(shù)據(jù)頁由IU鎖變成了IX鎖,并且把1:89:0(slot為0的記錄)由U鎖變成X鎖,變成X鎖                       后,就排斥了其他所有的鎖,這時候就可以進(jìn)行Update操作了。

 第四步: 后面就繼續(xù)90號數(shù)據(jù)頁,步驟類似,第二步和第三步。

不知道細(xì)心的你有沒有發(fā)現(xiàn),在Released Object之前我們才釋放1:89:0的X鎖,然后釋放89號數(shù)據(jù)頁的IX鎖,這說明什么???說明這個Update是貫穿于這個事務(wù)的,不像Select操作中,掃完一個數(shù)據(jù)頁就釋放一個數(shù)據(jù)頁。


3. 最后再看一個DELETE FROM dbo.Person WHERE ID=3 的操作。


  大概掃了一下上面的圖,或許你感覺和Update操作大差不差,會掃描數(shù)據(jù)頁中的每個記錄并加上U鎖。當(dāng)在1:89:0槽位中找到了目標(biāo)記錄后,然后將U鎖轉(zhuǎn)化為X鎖,具體可以參考Update。

2 深入的探討鎖機(jī)制

  上一篇我只是做了一個堆表讓大家初步的認(rèn)識到鎖的痙攣狀態(tài),但是在現(xiàn)實世界上并沒有這么簡單的事情,起碼我的表不會沒有索引對吧,,,還有就是我的表一定會有很多的連接過來,10:1的讀寫,很多碼農(nóng)可能都會遇到類似神乎其神的死鎖,卡住,讀不出來,插不進(jìn)入等等神仙的事情導(dǎo)致性能低下,這篇我們一起來探討下。

 

一: 當(dāng)select遇到性能低下的update會怎么樣?

1. 還是使用原始的person表,插入6條數(shù)據(jù),由于是4000字節(jié),所以兩條數(shù)據(jù)就是一個數(shù)據(jù)頁,如下圖:

1 DROP TABLE dbo.Person
2 CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'aaaaa')
3 --插入6條數(shù)據(jù),剛好3個數(shù)據(jù)頁
4 INSERT INTO dbo.Person DEFAULT VALUES
5 go 6

 

2. 為了模擬性能低下的Update操作,我們開個顯式事務(wù)來更新ID=4的記錄,并且用profile看一下,如下圖:

1 BEGIN TRAN
2 UPDATE dbo.Person SET NAME='bbbbb' WHERE id=4



3. 然后我們開下另一個會話連接,讀取ID=6的記錄會是怎樣?好奇嗎?

1 SELECT * FROM Person WHERE ID=6


從上面流程你是否看到,當(dāng)掃描到89號數(shù)據(jù)頁的slot1槽位的時候卡住了。。。我想你應(yīng)該知道update正好已經(jīng)給這條記錄加上了X鎖。。。如果你夠細(xì)心,你還會發(fā)現(xiàn),給S鎖附加記錄的條件是在當(dāng)引擎發(fā)現(xiàn)記錄所在的數(shù)據(jù)頁已經(jīng)附加上了IX鎖的情況下,才給該號數(shù)據(jù)頁下的每條記錄附加S鎖,對吧。。。好了,既然在Profile上面看不到了,我還是有其他辦法來判斷到底select語句現(xiàn)在處于什么狀態(tài)。

 

4. 使用sys.dm_tran_locks來看當(dāng)前各個連接持有鎖的狀態(tài)。

復(fù)制代碼
1 SELECT  l.request_session_id,
2         DB_NAME(l.resource_database_id),OBJECT_NAME(p.object_id),
3         l.resource_description,l.request_type,
4         l.request_status,request_mode 
5 FROM sys.dm_tran_locks AS l
6 LEFT JOIN sys.partitions AS p
7 ON l.resource_associated_entity_id=p.hobt_id
復(fù)制代碼


仔細(xì)觀察上圖可以看到,當(dāng)前有51和52號會話,51號在1:89:1槽位上使用了X鎖并且沒有釋放,52號此時也進(jìn)入了1:89:1中,并且想給該RowID附加S鎖,但是你也知道S和X鎖是排斥的,所以很無奈的一直保持等待狀態(tài)。

 

二:使用索引或許可以幫你逃過一劫

  當(dāng)你看完上面的講敘,是不是有點害怕???要是在生產(chǎn)環(huán)境下出現(xiàn)了這種情況,那我們是不是死的很慘???那接下來使用索引是不是真的可以幫我們躲過一劫呢?下面跟我一起看一看。


1. 新建索引index

1 -- 在ID列上建一個index
2 CREATE INDEX idx_person ON dbo.Person(ID)


2. 然后我們看下數(shù)據(jù)頁的分布情況,可以看到下圖中78,89,90是表數(shù)據(jù)頁,93號為索引數(shù)據(jù)頁。

1 DBCC TRACEON(2588,3604)
2 DBCC IND(Ctrip,Person,-1)


 

3. 麻蛋的,繼續(xù)執(zhí)行上面的那個慢update

BEGIN TRAN
UPDATE dbo.Person SET NAME='bbbbb' WHERE id=4

 

4. 激動人心的時刻來了,由于數(shù)據(jù)太少,所以我這里強(qiáng)制讓引擎執(zhí)行我創(chuàng)建的索引,看看結(jié)果怎樣?



居然沒卡?。???現(xiàn)在是不是有一股強(qiáng)烈的好奇心來了,狗狗狗。。。馬上開啟profile,看看到底都發(fā)生了什么?


仔細(xì)看完這個圖,是不是覺得很有意思呢???具體步驟如下:

第一步:給表(Object)加上IS鎖。

第二步:因為要走索引,給93號索引數(shù)據(jù)頁加上IS鎖。

第三步:找到93號索引數(shù)據(jù)頁的目標(biāo)key,給這個key加上S鎖,有人可能就會問了。。。這個key不就是6嘛,為什么這個key=(61005a25560e),你要是太好奇我可以告                   訴你,年輕人說話不要太屌,每行索引記錄都有一個散列值,這個值就是根據(jù)索引的幾個字段散列出來的,好處就是防止你的索引長度過大,導(dǎo)致鎖這個記錄的                   時候太耗費鎖空間了。。。。如果你還是不太相信的話,我用DBCC給你看一看。      

            

第四步:根據(jù)這個key直接跳到存放記錄的90號數(shù)據(jù)頁中,萬幸的是update的記錄剛好不在90號數(shù)據(jù)頁中。。。。就這樣躲過一劫了。。。然后select順利的讀取到了該                  讀的記錄,最后釋放相關(guān)的IS鎖。

3 nolock引發(fā)的三級事件的一些思考

  曾今有件事情讓我記憶猶新,那年剛來攜程不久,馬上就被安排寫一個接口,供企鵝公司調(diào)用他們員工的差旅信息,然后我就三下五除二的給寫好了,上線之后,大概過了一個月。。。DBA那邊報告數(shù)據(jù)庫出現(xiàn)大量鎖超時,并且及時根據(jù)sql的來源將email發(fā)到了我們部門,指出sql讀取時間過長,并且缺少nolock,影響了大量機(jī)票訂單入庫,然后我就拿著sql去生產(chǎn)環(huán)境跑了下,22s。。?;ú?。。。項目上線時間太久,版本已經(jīng)不存在了,無法回滾。。。原本準(zhǔn)備撤下接口。。??戳讼鲁废陆涌诟由蟦olock時間相差不多,最后決定先加上nolock,發(fā)布緊急單。。。然后再優(yōu)化,DBA那邊暫時做手工解鎖,發(fā)上去后,最后就是損失XXXX訂單。。。定級為三級事件。然后就是追責(zé),當(dāng)然這個責(zé)任只能有老大們?nèi)コ袚?dān)了,出了這次由我引發(fā)的事件,我得思考了,出了事情對我不見得全是壞事,起碼這次會讓我銘記如心,想想也搓,來攜程之前根本就不會關(guān)注要不要給select指定nolock,這其中也包括自己沒遇到過大數(shù)據(jù)吧,也包括自己的能力有限,只知道有鎖這個玩意,細(xì)說的話就啥也不知道了,后來才知道攜程有個規(guī)則,就是很多業(yè)務(wù)產(chǎn)線所寫的select都必須指定nolock,懂一點的人可能會說nolock可以提升性能,如果你這樣說,確實是這樣,因為數(shù)據(jù)庫的鎖是有96字節(jié)開銷的,沒了鎖,也就沒有你在profile中看到accquired和released痙攣了,當(dāng)你看完我的事件之后,你可能會意識到,性能提升不是最關(guān)心的,最關(guān)心就是不要出現(xiàn)死鎖,鎖等待。。。好了,言歸正傳,下面我們看看到底在數(shù)據(jù)庫中可以指定多少個鎖???


一:到底可以指定多少個鎖

  這個問題有意思,我們不需要記,只要你裝一個SQL Prompt,有了這個神器,你就知道到底有多少個?如下圖:

1 DROP TABLE dbo.Person
2 CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'xxxxx')
3 INSERT INTO dbo.Person DEFAULT VALUES
4 go 6


一眼掃下去,還是蠻多的,不過你要注意了,那些所謂的XXXLock才是我們需要關(guān)注的,根據(jù)上面的圖,我們大概把鎖分個類。。。

粒度鎖:PAGLOCK, TABLOCK, TABLOCKX, ROWLOCK, NOLOCK

模式鎖:HOLDLOCK, UPDLOCK, XLOCK

接下來我從粒度鎖說起:

1. NOLOCK

  都說nolock是無鎖模式的,那到底是怎樣的無鎖呢???到這篇為止,你應(yīng)該知道,如果不加nolock,我們的表,數(shù)據(jù)頁是附加IS鎖的,那接下來我用profile看下兩者有什么區(qū)別?!?/p>

 

從上圖中,你會看到加上nolock之后,object上面附加了Sch-S鎖,這個鎖叫做“架構(gòu)穩(wěn)定鎖”,很簡單就是sql編譯時附加的一把鎖,目的就是防止在編譯時,有其他連接修改表結(jié)構(gòu),而這個鎖只與Sch-M鎖沖突,與其他鎖都兼容,這說明什么?說明其他連接鎖住了記錄也沒關(guān)系,我的nolock不跟他們打交道,這樣的話,就可能會讀到臟數(shù)據(jù),不過沒關(guān)系,攜程的很多業(yè)務(wù)是容許臟數(shù)據(jù)的,畢竟比鎖等待,死鎖要強(qiáng)得多,再說nolock讀到了其他連接未修改或者未提交的數(shù)據(jù),這個概率也比較低,就算遇到了也沒關(guān)系,一般不會招來客訴的,客人或許再刷下頁面,數(shù)據(jù)或許就正確了,對不對。。。

 

2.TABLOCK

  這個還是比較見名識義的,就是附加在table上的鎖,也就是表鎖了,很恐怖的。。。下面我舉個Update的例子,看看前后對比。


在上面你有沒有看到,X鎖已經(jīng)附加到OBJECT上面去了。。。這樣的話,其他連接就動不了這個Object了,只能等待。。。

 

3.  PAGLOCK

  看了名字你應(yīng)該也知道,就是附加到頁面這個級別的鎖,我也舉一個Update的例子。

1 BEGIN TRAN
2 UPDATE dbo.Person SET NAME='aaaaa' WHERE ID=6
3 
4 BEGIN TRAN
5 UPDATE dbo.Person WITH(PAGLOCK) SET NAME='bbbbb' WHERE ID=4



從上面兩個圖中,你應(yīng)該可以看到,原來附加到RID上面的U鎖,由于PagLock的提升,現(xiàn)在要附加到Page上面了,這個就是所謂的數(shù)據(jù)頁鎖。

 

4.TABLOCKX, ROWLOCK

   這兩個我就不細(xì)說了,TABLOCKX就是直接附加在table上的X鎖,你可以通過select看一下。


ROWLOCK的話,默認(rèn)情況下就是ROWLOCK,比如默認(rèn)的Update,你會發(fā)現(xiàn)RID上被附加的U鎖,這個就是行鎖。

 

5.UPDLOCK

 這個鎖還是蠻有意思的,它就是update鎖,如果你select下,它會呈現(xiàn)update的鎖痙攣效果。


  

6. XLOCK

  知道了UPDLOCK鎖,我想XLOCK你也應(yīng)該明白了。。。它就是delete鎖,即排他鎖,我可以讓select帶上排他鎖。


 

7.HOLDLOCK

  最后一個我也沒鬧明白,據(jù)說是讓語句在整個事務(wù)中持有鎖,然后我就用select和update調(diào)試一下。

1 SELECT * FROM dbo.Person(HOLDLOCK)
2 UPDATE dbo.Person WITH(HOLDLOCK) SET NAME='bbbbb' WHERE ID=4


三 SQL Server 鎖機(jī)制 悲觀鎖 樂觀鎖 實測解析


在使用SQL時,大都會遇到這樣的問題,你Update一條記錄時,需要通過Select來檢索出其值或條件,然后在通過這個值來執(zhí)行修改操作。

但當(dāng)以上操作放到多線程中并發(fā)處理時會出現(xiàn)問題:某線程select了一條記錄但還沒來得及update時,另一個線程仍然可能會進(jìn)來select到同一條記錄。

 一般解決辦法就是使用鎖和事物的聯(lián)合機(jī)制:


1. 把select放在事務(wù)中, 否則select完成, 鎖就釋放了
2. 要阻止另一個select , 則要手工加鎖, select 默認(rèn)是共享鎖, select之間的共享鎖是不沖突的, 所以, 如果只是共享鎖, 即使鎖沒有釋放, 另一個select一樣可以下共享鎖, 從而select出數(shù)據(jù) 
BEGIN TRAN
SELECT * FROM Table WITH(UPDLOCK) 
--或者 SELECT * FROM Table WITH(TABLOCKX, READPAST) 具體情況而定。
UPDATE ....
COMMIT TRAN

所有Select加 With (NoLock)解決阻塞死鎖,在查詢語句中使用 NOLOCK 和 READPAST 
處理一個數(shù)據(jù)庫死鎖的異常時候,其中一個建議就是使用 NOLOCK 或者 READPAST 。有關(guān) NOLOCK 和 READPAST的一些技術(shù)知識點: 
對于非銀行等嚴(yán)格要求事務(wù)的行業(yè),搜索記錄中出現(xiàn)或者不出現(xiàn)某條記錄,都是在可容忍范圍內(nèi),所以碰到死鎖,應(yīng)該首先考慮,我們業(yè)務(wù)邏輯是否能容忍出現(xiàn)或者不出現(xiàn)某些記錄,而不是尋求對雙方都加鎖條件下如何解鎖的問題。 
NOLOCK 和 READPAST 都是處理查詢、插入、刪除等操作時候,如何應(yīng)對鎖住的數(shù)據(jù)記錄。但是這時候一定要注意NOLOCK 和 READPAST的局限性,確認(rèn)你的業(yè)務(wù)邏輯可以容忍這些記錄的出現(xiàn)或者不出現(xiàn): 
簡單來說:

1.NOLOCK 可能把沒有提交事務(wù)的數(shù)據(jù)也顯示出來
2.READPAST 會把被鎖住的行不顯示出來

不使用 NOLOCK 和 READPAST ,在 Select 操作時候則有可能報錯誤:事務(wù)(進(jìn)程 ID **)與另一個進(jìn)程被死鎖在 鎖 資源上,并且已被選作死鎖犧牲品。

SELECT * FROM Table WITH(NOLOCK)
SELECT * FROM Table WITH(READPAST)

實際開始動手用代碼說話吧!

SQLServer2012在查詢分析器里面開兩個連接

插入鎖:


結(jié)論:“表鎖”鎖定對該表的Select、Update、Delete操作,但不影響對該表的Insert操作也不影響以主鍵Id為條件的Select,所以Select如果不想等待就要在Select后加With(Nolock),但這樣會產(chǎn)生臟數(shù)據(jù)就是其他事務(wù)已更新但并沒有提交的數(shù)據(jù),如果該事務(wù)進(jìn)行了RollBack則取出的數(shù)據(jù)就是錯誤的,所以好自己權(quán)衡利弊,一般情況下90%以上的Select都允許臟讀,只有賬戶金額相關(guān)的不允許。

------------------A連接 Insert Lock-------------------
BEGIN TRAN
INSERT INTO dbo.UserInfo
        ( Name, Age, Mobile, AddTime, Type )
VALUES  ( 'eee', -- Name - varchar(50)
          2, -- Age - int
          '555', -- Mobile - char(11)
          GETDATE(), -- AddTime - datetime
          0  -- Type - int
          )
          
SELECT resource_type, request_mode,COUNT(*)  FROM sys.dm_tran_locks
WHERE request_session_id=@@SPID
GROUP BY resource_type,request_mode
--ROLLBACK TRAN

------------------------B連接 Insert Lock------------------------
INSERT INTO dbo.UserInfo
        ( Name, Age, Mobile, AddTime, Type )
VALUES  ( 'fff', -- Name - varchar(50)
          2, -- Age - int
          '123', -- Mobile - char(11)
          GETDATE(), -- AddTime - datetime
          1  -- Type - int
          ) --可以執(zhí)行插入
          
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Age=1 --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=3 --可以執(zhí)行查詢(根據(jù)主鍵可以)
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(在一個事務(wù)中,有更新字段但還沒有提交,此時就會查處臟數(shù)據(jù))
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Age=1 --可以執(zhí)行查詢
UPDATE dbo.UserInfo SET Type=5 WHERE Name='fff'  --需要等待解鎖
DELETE FROM dbo.UserInfo WHERE Name='fff' --需要等待解鎖

更新鎖:


結(jié)論:“表鎖”鎖定對該表的Select、Update、Delete操作,但不影響對該表的Insert操作也不影響以主鍵Id為條件的Select

-----------------------A連接 Update Lock-----------------------
BEGIN TRAN
UPDATE dbo.UserInfo SET Name = 'eee' WHERE Age = 2

SELECT resource_type, request_mode,COUNT(*)  FROM sys.dm_tran_locks
WHERE request_session_id=@@SPID
GROUP BY resource_type,request_mode

--ROLLBACK TRAN

------------------------B連接 Update Lock------------------------
INSERT INTO dbo.UserInfo
        ( Name, Age, Mobile, AddTime, Type )
VALUES  ( 'ppp', -- Name - varchar(50)
          15, -- Age - int
          '666', -- Mobile - char(11)
          GETDATE(), -- AddTime - datetime
          9  -- Type - int
          ) --可以執(zhí)行插入
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Name='ppp' --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=3 --可以執(zhí)行查詢(根據(jù)主鍵可以)
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(在一個事務(wù)中,有更新字段但還沒有提交,此時就會查處臟數(shù)據(jù))
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'ppp' --可以執(zhí)行查詢
UPDATE dbo.UserInfo SET Age=8 WHERE Name='ccc' --需要等待解鎖
DELETE dbo.UserInfo WHERE Age = 5 --需要等待解鎖

主鍵鎖:


結(jié)論:“行鎖+表鎖” 鎖定對該表的Select、Update、Delete操作,但不影響對該表的Insert操作也不影響以主鍵Id為條件的Select、Update、Delete

------------------------A連接 Key Lock--------------------
BEGIN TRAN
UPDATE dbo.UserInfo SET Name='hhh' WHERE Id=3 --以主鍵為條件

SELECT resource_type, request_mode,COUNT(*)  FROM sys.dm_tran_locks
WHERE request_session_id=@@SPID
GROUP BY resource_type,request_mode

--ROLLBACK TRAN

------------------------B連接 Key Lock----------------------
INSERT INTO dbo.UserInfo
        ( Name, Age, Mobile, AddTime, Type )
VALUES  ( 'kkk', -- Name - varchar(50)
          18, -- Age - int
          '234', -- Mobile - char(11)
          GETDATE(), -- AddTime - datetime
          7  -- Type - int
          ) --可以執(zhí)行插入
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(在一個事務(wù)中,有更新字段但還沒有提交,此時就會查處臟數(shù)據(jù))
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk' --可以執(zhí)行查詢

-----//全表查詢及操作正在處理的行
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=3 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=3 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
DELETE dbo.UserInfo WHERE Id=3 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
-----//使用非主鍵為條件的操作
SELECT * FROM dbo.UserInfo WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
DELETE dbo.UserInfo WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
-----//使用主鍵為條件的操作
SELECT * FROM dbo.UserInfo WHERE id=1 --可以執(zhí)行查詢(根據(jù)主鍵可以)
UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
DELETE dbo.UserInfo WHERE Id=1 --可以執(zhí)行刪除(根據(jù)主鍵可以)

索引鎖:


結(jié)論:“行鎖+表鎖” 鎖定對該表的Select、Update、Delete操作,但不影響對該表的Insert操作也不影響以主鍵Id為條件的Select、Update、Delete,也不影響以索引列Name為條件的Update、Delete但不可以Select

------------------------A連接 Index Lock--------------------
DROP INDEX dbo.UserInfo.Index_UserInfo_Name
CREATE INDEX Index_UserInfo_Name ON dbo.UserInfo(Name)

BEGIN TRAN
UPDATE dbo.UserInfo SET age=66 WHERE Name='ddd' --使用name索引列為條件

SELECT resource_type, request_mode,COUNT(*)  FROM sys.dm_tran_locks
WHERE request_session_id=@@SPID
GROUP BY resource_type,request_mode

--ROLLBACK TRAN

----------------------B連接 Index Lock-------------------
INSERT INTO dbo.UserInfo
        ( Name, Age, Mobile, AddTime, Type )
VALUES  ( 'iii', -- Name - varchar(50)
          20, -- Age - int
          '235235235', -- Mobile - char(11)
          GETDATE(), -- AddTime - datetime
          12  -- Type - int
          ) --可以執(zhí)行插入
SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(在一個事物中,有更新字段但還沒有提交,此時就會查處臟數(shù)據(jù))
SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk' --可以執(zhí)行查詢

-----//全表查詢及操作正在處理的行
SELECT * FROM dbo.UserInfo --需要等待解鎖
SELECT * FROM dbo.UserInfo WHERE Id=4 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=4 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
DELETE dbo.UserInfo WHERE Id=4 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
-----//使用非主鍵非索引為條件的操作
SELECT * FROM dbo.UserInfo WHERE Age=5 --需要等待解鎖(非主鍵不可)
UPDATE dbo.UserInfo SET Name='ooo' WHERE Age=5 --需要等待解鎖(非主鍵不可)
DELETE dbo.UserInfo WHERE Age=5 --需要等待解鎖(非主鍵不可)
-----//使用主鍵為條件的操作
SELECT * FROM dbo.UserInfo WHERE Id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
DELETE dbo.UserInfo WHERE Id=1 --可以執(zhí)行刪除(根據(jù)主鍵可以)
-----//使用索引為條件的操作
SELECT * FROM dbo.UserInfo WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa' --可以執(zhí)行更新(根據(jù)索引可以)
DELETE dbo.UserInfo WHERE Name='aaa' --可以執(zhí)行刪除(根據(jù)索引可以)

悲觀鎖(更新鎖-人工手動設(shè)置上鎖):


結(jié)論:可以理解為在使用版本控制軟件的時候A遷出了一個文件,并且8i將這個87文件鎖定,B就無法再遷出該文件了,直到A遷入解鎖后才能被其他人遷出。

------------------------A連接 Update Lock(悲觀鎖)---------------------
BEGIN TRAN
SELECT * FROM dbo.UserInfo WITH(UPDLOCK) WHERE Id=2

SELECT resource_type, request_mode,COUNT(*)  FROM sys.dm_tran_locks
WHERE request_session_id=@@SPID
GROUP BY resource_type,request_mode

--COMMIT TRAN
--ROLLBACK TRAN

---------------------------B連接 Update Lock(悲觀鎖)-------------------------
SELECT * FROM dbo.UserInfo --可以執(zhí)行查詢
SELECT * FROM dbo.UserInfo WHERE id=2 --可以執(zhí)行查詢
SELECT * FROM dbo.UserInfo WHERE Name='ooo' --可以執(zhí)行查詢

UPDATE dbo.UserInfo SET Age=3 WHERE id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
UPDATE dbo.UserInfo SET Age=3 WHERE Name='ccc' --需要等待解鎖(非主鍵不可)

DELETE dbo.UserInfo WHERE id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
DELETE dbo.UserInfo WHERE name='ccc' --需要等待解鎖(非主鍵不可)

樂觀鎖(人工通過邏輯在數(shù)據(jù)庫中模擬鎖)


結(jié)論:可以理解為同樣在使用版本控制軟件的時候A遷出了一個文件,B也可以遷出該文件,兩個人都可以對此文件進(jìn)行修改,其中一個人先進(jìn)行提交的時候,版本并沒有變化所以可以正常提交,另一個后提交的時候,發(fā)現(xiàn)版本增加不對稱了,就提示沖突由用戶來選擇如何進(jìn)行合并再重新進(jìn)行提交。

--------------------------A客戶端連接 Lock(樂觀鎖)------------------------
--DROP TABLE Coupon
-----------------創(chuàng)建優(yōu)惠券表-----------------
CREATE TABLE Coupon
(
    Id INT PRIMARY KEY IDENTITY(1,1),
    Number VARCHAR(50) NOT NULL,
    [User] VARCHAR(50),
    UseTime DATETIME,
    IsFlag BIT DEFAULT(0) NOT NULL,
    CreateTime DATETIME DEFAULT(GETDATE()) NOT NULL
)
INSERT INTO dbo.Coupon(Number) VALUES ( '10000001')
INSERT INTO dbo.Coupon(Number) VALUES ( '10000002')
INSERT INTO dbo.Coupon(Number) VALUES ( '10000003')
INSERT INTO dbo.Coupon(Number) VALUES ( '10000004')
INSERT INTO dbo.Coupon(Number) VALUES ( '10000005')
INSERT INTO dbo.Coupon(Number) VALUES ( '10000006')

--SELECT * FROM dbo.Coupon WITH(NOLOCK) --查詢數(shù)據(jù)
--UPDATE Coupon SET [User]=NULL, UseTime=NULL, IsFlag=0 --還原數(shù)據(jù)

-----------------1、模擬高并發(fā)普通更新-----------------
DECLARE @User VARCHAR(50)    --模擬要使用優(yōu)惠券的用戶
DECLARE @TempId INT            --模擬抽選出來的要使用的優(yōu)惠券
SET @User='a'
BEGIN TRAN
SELECT @TempId=Id FROM dbo.Coupon WHERE IsFlag=0    --高并發(fā)時此語句有可能另外一個該事務(wù)已取出的Id
--WAITFOR DELAY '00:00:05'    --改用此方式要開兩個SQL Management客戶端
UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
COMMIT TRAN
--ROLLBACK TRAN

-----------------2、悲觀鎖解決方案-----------------
DECLARE @User VARCHAR(50)    --模擬要使用優(yōu)惠券的用戶
DECLARE @TempId INT            --模擬抽選出來的要使用的優(yōu)惠券
SET @User='a'
BEGIN TRAN
SELECT @TempId=Id FROM dbo.Coupon WITH(UPDLOCK) WHERE IsFlag=0    --高并發(fā)時此語句會鎖定取出的Id數(shù)據(jù)行
--WAITFOR DELAY '00:00:05'    --改用此方式要開兩個SQL Management客戶端
UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
COMMIT TRAN
--ROLLBACK TRAN

-----------------3、樂觀鎖解決方案-----------------
ALTER TABLE dbo.Coupon ADD RowVer ROWVERSION NOT NULL --增加數(shù)據(jù)行版本戳類型字段(微軟新推薦數(shù)據(jù)字段,該字段每張表只能有一個,會在創(chuàng)建行或更新行時自動進(jìn)行修改無需人為干涉,該字段不能建立索引及主鍵因為會頻繁修改)


DECLARE @User VARCHAR(50)    --模擬要使用優(yōu)惠券的用戶
DECLARE @TempId INT            --模擬抽選出來的要使用的優(yōu)惠券
DECLARE @RowVer BINARY(8)    --抽選出來的優(yōu)惠券的版本(ROWVERSION數(shù)據(jù)類型存儲大小為8字節(jié))
SET @User='a'

BEGIN TRY
    BEGIN TRAN
    SELECT @TempId=Id, @RowVer=RowVer FROM dbo.Coupon WHERE IsFlag=0    --取出可用的Id及對應(yīng)的版本戳
    --WAITFOR DELAY '00:00:05'    --改用此方式要開兩個SQL Management客戶端
    UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId AND RowVer=@RowVer
    IF(@@ROWCOUNT > 0)
        BEGIN
            PRINT('修改成功')
            COMMIT TRAN
        END
    ELSE
        BEGIN
            PRINT('該數(shù)據(jù)已被其他用戶修改')
            ROLLBACK TRAN
        END
END TRY
BEGIN CATCH
    ROLLBACK TRAN
END CATCH

--------------------------B客戶端連接 Lock(樂觀鎖)------------------------
--此測試需要開兩個SQL Management Studio客戶端,在A客戶端使用WAITFOR DELAY來模擬并發(fā)占用,在B客戶端執(zhí)行與A客戶端相同的SQL腳本即可(注釋掉WAITFOR),所以在此不放相同代碼了。


在樂觀鎖和悲觀鎖之間進(jìn)行選擇的標(biāo)準(zhǔn)是:沖突的頻率與嚴(yán)重性。如果沖突很少,或者沖突的后果不會很嚴(yán)重,那么通常情況下應(yīng)該選擇樂觀鎖,因為它能得到更好的并發(fā)性,而且更容易實現(xiàn)。但是,如果沖突的結(jié)果對于用戶來說痛苦的,那么就需要使用悲觀策略。

我認(rèn)為如果同一張表的并發(fā)很高,但并發(fā)處理同一條數(shù)據(jù)的沖突幾率很低,那就應(yīng)該使用樂觀鎖,反之,如果同一張表的并發(fā)不高,但同時處理同一條數(shù)據(jù)的幾率很高,就應(yīng)該使用悲觀鎖。

四 SQL Server 中WITH (NOLOCK)淺析


概念介紹

開發(fā)人員喜歡在SQL腳本中使用WITH(NOLOCK), WITH(NOLOCK)其實是表提示(table_hint)中的一種。它等同于 READUNCOMMITTED 。 具體的功能作用如下所示(摘自MSDN):

   1: 指定允許臟讀。不發(fā)布共享鎖來阻止其他事務(wù)修改當(dāng)前事務(wù)讀取的數(shù)據(jù),其他事務(wù)設(shè)置的排他鎖不會阻礙當(dāng)前事務(wù)讀取鎖定數(shù)據(jù)。允許臟讀可能產(chǎn)生較多的并發(fā)操作,但其代價是讀取以后會被其他事務(wù)回滾的數(shù)據(jù)修改。這可能會使您的事務(wù)出錯,向用戶顯示從未提交過的數(shù)據(jù),或者導(dǎo)致用戶兩次看到記錄(或根本看不到記錄)。有關(guān)臟讀、不可重復(fù)讀和幻讀的詳細(xì)信息,請參閱并發(fā)影響。

   2: READUNCOMMITTED 和 NOLOCK 提示僅適用于數(shù)據(jù)鎖。所有查詢(包括那些帶有 READUNCOMMITTED 和 NOLOCK 提示的查詢)都會在編譯和執(zhí)行過程中獲取 Sch-S(架構(gòu)穩(wěn)定性)鎖。因此,當(dāng)并發(fā)事務(wù)持有表的 Sch-M(架構(gòu)修改)鎖時,將阻塞查詢。例如,數(shù)據(jù)定義語言 (DDL) 操作在修改表的架構(gòu)信息之前獲取 Sch-M 鎖。所有并發(fā)查詢(包括那些使用 READUNCOMMITTED 或 NOLOCK 提示運行的查詢)都會在嘗試獲取 Sch-S 鎖時被阻塞。相反,持有 Sch-S 鎖的查詢將阻塞嘗試獲取 Sch-M 鎖的并發(fā)事務(wù)。有關(guān)鎖行為的詳細(xì)信息,請參閱鎖兼容性(數(shù)據(jù)庫引擎)。

   3:  不能為通過插入、更新或刪除操作修改過的表指定 READUNCOMMITTED 和 NOLOCK。SQL Server 查詢優(yōu)化器忽略 FROM 子句中應(yīng)用于 UPDATE 或 DELETE 語句的目標(biāo)表的 READUNCOMMITTED 和 NOLOCK 提示。

功能與缺陷

    使用WIHT(NOLOCK)有利也有弊,所以在決定使用之前,你一定需要了解清楚WITH(NOLOCK)的功能和缺陷,看其是否適合你的業(yè)務(wù)需求,不要覺得它能提升性能,稀里糊涂的就使用它。

    1:使用WITH(NOLOCK)時查詢不受其它排他鎖阻塞

    打開會話窗口1,執(zhí)行下面腳本,不提交也不回滾事務(wù),模擬事務(wù)真在執(zhí)行過程當(dāng)中

BEGIN TRAN
 
       UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1;
 
       --ROLLBACK
 

   

   打開會話窗口2,執(zhí)行下面腳本,你會發(fā)現(xiàn)執(zhí)行結(jié)果一直查詢不出來(其實才兩條記錄)。當(dāng)前會話被阻塞了

SELECT * FROM TEST;

    打開會話窗口3,執(zhí)行下面腳本,查看阻塞情況,你會發(fā)現(xiàn)在會話2被會話1給阻塞了,會話2的等待類型為LCK_M_S:“當(dāng)某任務(wù)正在等待獲取共享鎖時出現(xiàn)”

 
 
  SELECT wt.blocking_session_id                    AS BlockingSessesionId
        ,sp.program_name                           AS ProgramName
        ,COALESCE(sp.LOGINAME, sp.nt_username)     AS HostName    
        ,ec1.client_net_address                    AS ClientIpAddress
        ,db.name                                   AS DatabaseName        
        ,wt.wait_type                              AS WaitType                    
        ,ec1.connect_time                          AS BlockingStartTime
        ,wt.WAIT_DURATION_MS/1000                  AS WaitDuration
        ,ec1.session_id                            AS BlockedSessionId
        ,h1.TEXT                                   AS BlockedSQLText
        ,h2.TEXT                                   AS BlockingSQLText
  FROM sys.dm_tran_locks AS tl
  INNER JOIN sys.databases db
    ON db.database_id = tl.resource_database_id
  INNER JOIN sys.dm_os_waiting_tasks AS wt
    ON tl.lock_owner_address = wt.resource_address
  INNER JOIN sys.dm_exec_connections ec1
    ON ec1.session_id = tl.request_session_id
  INNER JOIN sys.dm_exec_connections ec2
    ON ec2.session_id = wt.blocking_session_id
  LEFT OUTER JOIN master.dbo.sysprocesses sp
    ON SP.spid = wt.blocking_session_id
  CROSS APPLY sys.dm_exec_sql_text(ec1.most_recent_sql_handle) AS h1
  CROSS APPLY sys.dm_exec_sql_text(ec2.most_recent_sql_handle) AS h2

 


 

此時查看會話1(會話1的會話ID為53,執(zhí)行腳本1前,可以用SELECT  @@spid查看會話ID)的鎖信息情況,你會發(fā)現(xiàn)表TEST(ObjId=1893581784)持有的鎖信息如下所示


打開會話窗口4,執(zhí)行下面腳本.你會發(fā)現(xiàn)查詢結(jié)果很快就出來,會話4并不會被會話1阻塞。

    SELECT * FROM TEST WITH(NOLOCK)

從上面模擬的這個小例子可以看出,正是由于加上WITH(NOLOCK)提示后,會話1中事務(wù)設(shè)置的排他鎖不會阻礙當(dāng)前事務(wù)讀取鎖定數(shù)據(jù),所以會話4不會被阻塞,從而提升并發(fā)時查詢性能。

2:WITH(NOLOCK) 不發(fā)布共享鎖來阻止其他事務(wù)修改當(dāng)前事務(wù)讀取的數(shù)據(jù),這個就不舉例子了。

本質(zhì)上WITH(NOLOCK)是通過減少鎖和不受排它鎖影響來減少阻塞,從而提高并發(fā)時的性能。所謂凡事有利也有弊,WITH(NOLOCK)在提升性能的同時,也會產(chǎn)生臟讀現(xiàn)象。

如下所示,表TEST有兩條記錄,我準(zhǔn)備更新OBJECT_ID=1的記錄,此時事務(wù)既沒有提交也沒有回滾


BEGIN TRAN 
 
UPDATE TEST SET NAME='Timmy' WHERE OBJECT_ID =1; 
 
--ROLLBACK 
 

此時另外一個會話使用WITH(NOLOCK)查到的記錄為未提交的記錄值


假如由于某種原因,該事務(wù)回滾了,那么我們讀取到的OBJECT_ID=1的記錄就是一條臟數(shù)據(jù)。

臟讀又稱無效數(shù)據(jù)的讀出,是指在數(shù)據(jù)庫訪問中,事務(wù)T1將某一值修改,然后事務(wù)T2讀取該值,此后T1因為某種原因撤銷對該值的修改,這就導(dǎo)致了T2所讀取到的數(shù)據(jù)是無效的。

WITH(NOLOCK)使用場景

什么時候可以使用WITH(NOLOCK)? 什么時候不能使用WITH(NOLOCK),這個要視你系統(tǒng)業(yè)務(wù)情況,綜合考慮性能情況與業(yè)務(wù)要求來決定是否使用WITH(NOLOCK), 例如涉及到金融或會計成本之類的系統(tǒng),出現(xiàn)臟讀那是要產(chǎn)生嚴(yán)重問題的。關(guān)鍵業(yè)務(wù)系統(tǒng)也要慎重考慮。大體來說一般有下面一些場景可以使用WITH(NOLOCK)

   1: 基礎(chǔ)數(shù)據(jù)表,這些表的數(shù)據(jù)很少變更。

   2:歷史數(shù)據(jù)表,這些表的數(shù)據(jù)很少變更。

   3:業(yè)務(wù)允許臟讀情況出現(xiàn)涉及的表。

   4:數(shù)據(jù)量超大的表,出于性能考慮,而允許臟讀。

另外一點就是不要濫用WITH(NOLOCK),我發(fā)現(xiàn)有個奇怪現(xiàn)象,很多開發(fā)知道WITH(NOLOCK),但是有不了解臟讀,習(xí)慣性的使用WITH(NOLOCK)。

WITH(NOLOCK)與 NOLOCK區(qū)別

為了搞清楚WITH(NOLOCK)與NOLOCK的區(qū)別,我查了大量的資料,我們先看看下面三個SQL語句有啥區(qū)別

    SELECT * FROM TEST NOLOCK

    SELECT * FROM TEST (NOLOCK);

    SELECT * FROM TEST WITH(NOLOCK);

上面的問題概括起來也就是說NOLOCK、(NOLOCK)、 WITH(NOLOCK)的區(qū)別:

1: NOLOCK這樣的寫法,其實NOLOCK其實只是別名的作用,而沒有任何實質(zhì)作用。所以不要粗心將(NOLOCK)寫成NOLOCK

2:(NOLOCK)與WITH(NOLOCK)其實功能上是一樣的。(NOLOCK)只是WITH(NOLOCK)的別名,但是在SQL Server 2008及以后版本中,(NOLOCK)不推薦使用了,"不借助 WITH 關(guān)鍵字指定表提示”的寫法已經(jīng)過時了。 具體參見MSDN http://msdn.microsoft.com/zh-cn/library/ms143729%28SQL.100%29.aspx

    2.1  至于網(wǎng)上說WITH(NOLOCK)在SQL SERVER 2000不生效,我驗證后發(fā)現(xiàn)完全是個謬論。

    2.2  在使用鏈接服務(wù)器的SQL當(dāng)中,(NOLOCK)不會生效,WITH(NOLOCK)才會生效。如下所示


    消息 4122,級別 16,狀態(tài) 1,第 1 行

    Remote table-valued function calls are not allowed.

3.語法上有些許出入,如下所示

這種語法會報錯
SELECT  * FROM   sys.indexes  WITH(NOLOCK) AS i
-Msg 156, Level 15, State 1, Line 1
-Incorrect syntax near the keyword 'AS'.
 
這種語法正常
SELECT  * FROM   sys.indexes  (NOLOCK) AS i
 
可以全部改寫為下面語法
 
SELECT  * FROM   sys.indexes   i WITH(NOLOCK) 
 
 
SELECT  * FROM   sys.indexes   i (NOLOCK) 

WITH(NOLOCK)會不會產(chǎn)生鎖

    很多人誤以為使用了WITH(NOLOCK)后,數(shù)據(jù)庫庫不會產(chǎn)生任何鎖。實質(zhì)上,使用了WITH(NOLOCK)后,數(shù)據(jù)庫依然對該表對象生成Sch-S(架構(gòu)穩(wěn)定性)鎖以及DB類型的共享鎖, 如下所示,可以在一個會話中查詢一個大表,然后在另外一個會話中查看鎖信息(也可以使用SQL Profile查看會話鎖信息)

    不使用WTIH(NOLOCK)


  使用WITH(NOLOCK)


  從上可以看出使用WITH(NOLOCK)后,數(shù)據(jù)庫并不是不生成相關(guān)鎖。  對比可以發(fā)現(xiàn)使用WITH(NOLOCK)后,數(shù)據(jù)庫只會生成DB類型的共享鎖、以及TAB類型的架構(gòu)穩(wěn)定性鎖.

另外,使用WITH(NOLOCK)并不是說就不會被其它會話阻塞,依然可能會產(chǎn)生Schema Change Blocking

會話1:執(zhí)行下面SQL語句,暫時不提交,模擬事務(wù)正在執(zhí)行

BEGIN TRAN 
 
  ALTER TABLE TEST ADD Grade VARCHAR(10) ; 
 

會話2:執(zhí)行下面語句,你會發(fā)現(xiàn)會話被阻塞,截圖如下所示。

SELECT * FROM TEST WITH(NOLOCK)



    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多