乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

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

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

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


      (一). 為什么要引入鎖

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

      ◆丟失更新

      A,B兩個用戶讀同一數(shù)據(jù)并進行修改,其中一個用戶的修改結(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)多個會話在讀取、鎖定以及隨后可能進行的資源更新時發(fā)生常見形式的死鎖。

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

      意向鎖 用于建立鎖的層次結(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) 鎖以進行更新。由于兩個事務(wù)都要轉(zhuǎn)換為排它 (X) 鎖,并且每個事務(wù)都等待另一個事務(wù)釋放共享模式鎖,因此發(fā)生死鎖。

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


      ◆排它鎖

      排它 (X) 鎖可以防止并發(fā)事務(wù)對資源進行訪問。其它事務(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ù)對資源進行更新,但是其它事務(wù)可以通過獲取表級的 IS 鎖來讀取層次結(jié)構(gòu)中的底層資源。


      ◆獨占鎖:

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

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

      ◆更新鎖:

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


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

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

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


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

      三 鎖的粒度

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

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

      資源 描述

      RID 行標識符。用于單獨鎖定表中的一行。

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

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

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

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

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

      SQL Server 提供以下的鎖級別:

      DATABASE  -- 無論何時當(dāng)一個SQL Server 進程正在使用除master以外的數(shù)據(jù)庫時,Lock Manager為該進程授予數(shù)據(jù)庫級的鎖。數(shù)據(jù)庫級的鎖總是共享鎖,用于跟蹤何時數(shù)據(jù)庫在使用中,以防其他進程刪除該數(shù)據(jù)庫,將數(shù)據(jù)庫置為脫機,或者恢復(fù)數(shù)據(jù)庫。注意,由于master和tempdb數(shù)據(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í)行頁級別的維護如頁拆分后更新頁指針時,將會獲取頁級鎖。
      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語句?! ?nbsp;
      3. PAGLOCK:指定添加頁鎖(否則通??赡芴砑颖礞i)?!?nbsp;
      4. READCOMMITTED用與運行在提交讀隔離級別的事務(wù)相同的鎖語義執(zhí)行掃描。默認情況下,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。  
      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ù)沒有被其他用戶修改。

      五 鎖定時間的長短


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


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


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


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

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

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

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

      六 SQL Server 中鎖的自定義


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


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


      可以使用SET DEADLOCK_PRIORITY控制在發(fā)生死鎖情況時會話的反應(yīng)方式。如果兩個進程都鎖定數(shù)據(jù),并且直到其它進程釋放自己的鎖時,每個進程才能釋放自己的鎖,即發(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ā)生的機會就越少;將不是事務(wù)所管理的工作單元鎖必需的命令移出事務(wù)。;

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

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

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

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

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

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

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

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

      10 使用每個進程所需的最低級別的鎖隔離。比如說,如果臟讀是可接受的并且不要求結(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í)行以下語句

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

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


      ◆共享鎖

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

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

      ◆共享鎖

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

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

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

      1. begin tran
      2. select A,C from table1 where B='b2'
      3. update table1 set A='aa' where B='b2'
      4. commit tran

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

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

      ◆死鎖

      1. --在第一個連接中執(zhí)行以下語句
      2. begin tran
      3. update table1 set A='aa' where B='b2'
      4. waitfor delay '00:00:30'
      5. update table2 set D='d5' where E='e1'
      6. commit tran
      7. --在第二個連接中執(zhí)行以下語句
      8. begin tran
      9. update table2 set D='d5' where E='e1'
      10. waitfor delay '00:00:10'
      11. update table1 set A='aa' where B='b2'
      12. commit tran

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


      十 應(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.PersonCREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'aaaaa')--插入6條,生成3個數(shù)據(jù)頁INSERT INTO dbo.Person DEFAULT VALUESgo 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鎖么???這里沒加,是因為引擎進入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記錄,進入前就Acquired,退出后就Released,當(dāng)掃描完78號數(shù)據(jù)頁的所有RID后,再釋放78                  號數(shù)據(jù)頁的IU鎖,進入下一個數(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鎖                       后,就排斥了其他所有的鎖,這時候就可以進行Update操作了。

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

      不知道細心的你有沒有發(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槽位中找到了目標記錄后,然后將U鎖轉(zhuǎn)化為X鎖,具體可以參考Update。

      2 深入的探討鎖機制

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

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

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

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

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

      1 BEGIN TRAN2 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鎖。。。如果你夠細心,你還會發(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 l6 LEFT JOIN sys.partitions AS p7 ON l.resource_associated_entity_id=p.hobt_id
      復(fù)制代碼


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

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

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

      1. 新建索引index

      1 -- 在ID列上建一個index2 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 TRANUPDATE dbo.Person SET NAME='bbbbb' WHERE id=4

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


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


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

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

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

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

                  

      第四步:根據(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,影響了大量機票訂單入庫,然后我就拿著sql去生產(chǎn)環(huán)境跑了下,22s。。。花擦。。。項目上線時間太久,版本已經(jīng)不存在了,無法回滾。。。原本準備撤下接口。。??戳讼鲁废陆涌诟由蟦olock時間相差不多,最后決定先加上nolock,發(fā)布緊急單。。。然后再優(yōu)化,DBA那邊暫時做手工解鎖,發(fā)上去后,最后就是損失XXXX訂單。。。定級為三級事件。然后就是追責(zé),當(dāng)然這個責(zé)任只能有老大們?nèi)コ袚?dān)了,出了這次由我引發(fā)的事件,我得思考了,出了事情對我不見得全是壞事,起碼這次會讓我銘記如心,想想也搓,來攜程之前根本就不會關(guān)注要不要給select指定nolock,這其中也包括自己沒遇到過大數(shù)據(jù)吧,也包括自己的能力有限,只知道有鎖這個玩意,細說的話就啥也不知道了,后來才知道攜程有個規(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.Person2 CREATE TABLE Person(ID INT IDENTITY,NAME CHAR(4000) DEFAULT 'xxxxx')3 INSERT INTO dbo.Person DEFAULT VALUES4 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ù)的,畢竟比鎖等待,死鎖要強得多,再說nolock讀到了其他連接未修改或者未提交的數(shù)據(jù),這個概率也比較低,就算遇到了也沒關(guān)系,一般不會招來客訴的,客人或許再刷下頁面,數(shù)據(jù)或許就正確了,對不對。。。

      2.TABLOCK

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


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

      3.  PAGLOCK

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

      1 BEGIN TRAN2 UPDATE dbo.Person SET NAME='aaaaa' WHERE ID=63 4 BEGIN TRAN5 UPDATE dbo.Person WITH(PAGLOCK) SET NAME='bbbbb' WHERE ID=4



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

      4.TABLOCKX, ROWLOCK

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


      ROWLOCK的話,默認情況下就是ROWLOCK,比如默認的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 鎖機制 悲觀鎖 樂觀鎖 實測解析


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

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

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


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

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

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

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

      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ù)進行了RollBack則取出的數(shù)據(jù)就是錯誤的,所以好自己權(quán)衡利弊,一般情況下90%以上的Select都允許臟讀,只有賬戶金額相關(guān)的不允許。

      1. ------------------A連接 Insert Lock-------------------
      2. BEGIN TRAN
      3. INSERT INTO dbo.UserInfo
      4. ( Name, Age, Mobile, AddTime, Type )
      5. VALUES ( 'eee', -- Name - varchar(50)
      6. 2, -- Age - int
      7. '555', -- Mobile - char(11)
      8. GETDATE(), -- AddTime - datetime
      9. 0 -- Type - int
      10. )
      11. SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
      12. WHERE request_session_id=@@SPID
      13. GROUP BY resource_type,request_mode
      14. --ROLLBACK TRAN
      15. ------------------------B連接 Insert Lock------------------------
      16. INSERT INTO dbo.UserInfo
      17. ( Name, Age, Mobile, AddTime, Type )
      18. VALUES ( 'fff', -- Name - varchar(50)
      19. 2, -- Age - int
      20. '123', -- Mobile - char(11)
      21. GETDATE(), -- AddTime - datetime
      22. 1 -- Type - int
      23. ) --可以執(zhí)行插入
      24. SELECT * FROM dbo.UserInfo --需要等待解鎖
      25. SELECT * FROM dbo.UserInfo WHERE Age=1 --需要等待解鎖
      26. SELECT * FROM dbo.UserInfo WHERE Id=3 --可以執(zhí)行查詢(根據(jù)主鍵可以)
      27. SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(在一個事務(wù)中,有更新字段但還沒有提交,此時就會查處臟數(shù)據(jù))
      28. SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Age=1 --可以執(zhí)行查詢
      29. UPDATE dbo.UserInfo SET Type=5 WHERE Name='fff' --需要等待解鎖
      30. DELETE FROM dbo.UserInfo WHERE Name='fff' --需要等待解鎖

      更新鎖:


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

      1. -----------------------A連接 Update Lock-----------------------
      2. BEGIN TRAN
      3. UPDATE dbo.UserInfo SET Name = 'eee' WHERE Age = 2
      4. SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
      5. WHERE request_session_id=@@SPID
      6. GROUP BY resource_type,request_mode
      7. --ROLLBACK TRAN
      8. ------------------------B連接 Update Lock------------------------
      9. INSERT INTO dbo.UserInfo
      10. ( Name, Age, Mobile, AddTime, Type )
      11. VALUES ( 'ppp', -- Name - varchar(50)
      12. 15, -- Age - int
      13. '666', -- Mobile - char(11)
      14. GETDATE(), -- AddTime - datetime
      15. 9 -- Type - int
      16. ) --可以執(zhí)行插入
      17. SELECT * FROM dbo.UserInfo --需要等待解鎖
      18. SELECT * FROM dbo.UserInfo WHERE Name='ppp' --需要等待解鎖
      19. SELECT * FROM dbo.UserInfo WHERE Id=3 --可以執(zhí)行查詢(根據(jù)主鍵可以)
      20. SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(在一個事務(wù)中,有更新字段但還沒有提交,此時就會查處臟數(shù)據(jù))
      21. SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'ppp' --可以執(zhí)行查詢
      22. UPDATE dbo.UserInfo SET Age=8 WHERE Name='ccc' --需要等待解鎖
      23. DELETE dbo.UserInfo WHERE Age = 5 --需要等待解鎖

      主鍵鎖:


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

      1. ------------------------A連接 Key Lock--------------------
      2. BEGIN TRAN
      3. UPDATE dbo.UserInfo SET Name='hhh' WHERE Id=3 --以主鍵為條件
      4. SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
      5. WHERE request_session_id=@@SPID
      6. GROUP BY resource_type,request_mode
      7. --ROLLBACK TRAN
      8. ------------------------B連接 Key Lock----------------------
      9. INSERT INTO dbo.UserInfo
      10. ( Name, Age, Mobile, AddTime, Type )
      11. VALUES ( 'kkk', -- Name - varchar(50)
      12. 18, -- Age - int
      13. '234', -- Mobile - char(11)
      14. GETDATE(), -- AddTime - datetime
      15. 7 -- Type - int
      16. ) --可以執(zhí)行插入
      17. SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(在一個事務(wù)中,有更新字段但還沒有提交,此時就會查處臟數(shù)據(jù))
      18. SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk' --可以執(zhí)行查詢
      19. -----//全表查詢及操作正在處理的行
      20. SELECT * FROM dbo.UserInfo --需要等待解鎖
      21. SELECT * FROM dbo.UserInfo WHERE Id=3 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
      22. UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=3 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
      23. DELETE dbo.UserInfo WHERE Id=3 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
      24. -----//使用非主鍵為條件的操作
      25. SELECT * FROM dbo.UserInfo WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
      26. UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
      27. DELETE dbo.UserInfo WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
      28. -----//使用主鍵為條件的操作
      29. SELECT * FROM dbo.UserInfo WHERE id=1 --可以執(zhí)行查詢(根據(jù)主鍵可以)
      30. UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
      31. DELETE dbo.UserInfo WHERE Id=1 --可以執(zhí)行刪除(根據(jù)主鍵可以)

      索引鎖:


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

      1. ------------------------A連接 Index Lock--------------------
      2. DROP INDEX dbo.UserInfo.Index_UserInfo_Name
      3. CREATE INDEX Index_UserInfo_Name ON dbo.UserInfo(Name)
      4. BEGIN TRAN
      5. UPDATE dbo.UserInfo SET age=66 WHERE Name='ddd' --使用name索引列為條件
      6. SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
      7. WHERE request_session_id=@@SPID
      8. GROUP BY resource_type,request_mode
      9. --ROLLBACK TRAN
      10. ----------------------B連接 Index Lock-------------------
      11. INSERT INTO dbo.UserInfo
      12. ( Name, Age, Mobile, AddTime, Type )
      13. VALUES ( 'iii', -- Name - varchar(50)
      14. 20, -- Age - int
      15. '235235235', -- Mobile - char(11)
      16. GETDATE(), -- AddTime - datetime
      17. 12 -- Type - int
      18. ) --可以執(zhí)行插入
      19. SELECT * FROM dbo.UserInfo WITH(NOLOCK) --可以執(zhí)行查詢(在一個事物中,有更新字段但還沒有提交,此時就會查處臟數(shù)據(jù))
      20. SELECT * FROM dbo.UserInfo WITH(NOLOCK) WHERE Name = 'kkk' --可以執(zhí)行查詢
      21. -----//全表查詢及操作正在處理的行
      22. SELECT * FROM dbo.UserInfo --需要等待解鎖
      23. SELECT * FROM dbo.UserInfo WHERE Id=4 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
      24. UPDATE dbo.UserInfo SET Name='mmm' WHERE Id=4 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
      25. DELETE dbo.UserInfo WHERE Id=4 --需要等待解鎖(根據(jù)主鍵,但與A連接操作相同行不可)
      26. -----//使用非主鍵非索引為條件的操作
      27. SELECT * FROM dbo.UserInfo WHERE Age=5 --需要等待解鎖(非主鍵不可)
      28. UPDATE dbo.UserInfo SET Name='ooo' WHERE Age=5 --需要等待解鎖(非主鍵不可)
      29. DELETE dbo.UserInfo WHERE Age=5 --需要等待解鎖(非主鍵不可)
      30. -----//使用主鍵為條件的操作
      31. SELECT * FROM dbo.UserInfo WHERE Id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
      32. UPDATE dbo.UserInfo SET Name='yyy' WHERE Id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
      33. DELETE dbo.UserInfo WHERE Id=1 --可以執(zhí)行刪除(根據(jù)主鍵可以)
      34. -----//使用索引為條件的操作
      35. SELECT * FROM dbo.UserInfo WHERE Name='aaa' --需要等待解鎖(非主鍵不可)
      36. UPDATE dbo.UserInfo SET Name='ooo' WHERE Name='aaa' --可以執(zhí)行更新(根據(jù)索引可以)
      37. DELETE dbo.UserInfo WHERE Name='aaa' --可以執(zhí)行刪除(根據(jù)索引可以)

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


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

      1. ------------------------A連接 Update Lock(悲觀鎖)---------------------
      2. BEGIN TRAN
      3. SELECT * FROM dbo.UserInfo WITH(UPDLOCK) WHERE Id=2
      4. SELECT resource_type, request_mode,COUNT(*) FROM sys.dm_tran_locks
      5. WHERE request_session_id=@@SPID
      6. GROUP BY resource_type,request_mode
      7. --COMMIT TRAN
      8. --ROLLBACK TRAN
      9. ---------------------------B連接 Update Lock(悲觀鎖)-------------------------
      10. SELECT * FROM dbo.UserInfo --可以執(zhí)行查詢
      11. SELECT * FROM dbo.UserInfo WHERE id=2 --可以執(zhí)行查詢
      12. SELECT * FROM dbo.UserInfo WHERE Name='ooo' --可以執(zhí)行查詢
      13. UPDATE dbo.UserInfo SET Age=3 WHERE id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
      14. UPDATE dbo.UserInfo SET Age=3 WHERE Name='ccc' --需要等待解鎖(非主鍵不可)
      15. DELETE dbo.UserInfo WHERE id=1 --可以執(zhí)行更新(根據(jù)主鍵可以)
      16. DELETE dbo.UserInfo WHERE name='ccc' --需要等待解鎖(非主鍵不可)

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


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

      1. --------------------------A客戶端連接 Lock(樂觀鎖)------------------------
      2. --DROP TABLE Coupon
      3. -----------------創(chuàng)建優(yōu)惠券表-----------------
      4. CREATE TABLE Coupon
      5. (
      6. Id INT PRIMARY KEY IDENTITY(1,1),
      7. Number VARCHAR(50) NOT NULL,
      8. [User] VARCHAR(50),
      9. UseTime DATETIME,
      10. IsFlag BIT DEFAULT(0) NOT NULL,
      11. CreateTime DATETIME DEFAULT(GETDATE()) NOT NULL
      12. )
      13. INSERT INTO dbo.Coupon(Number) VALUES ( '10000001')
      14. INSERT INTO dbo.Coupon(Number) VALUES ( '10000002')
      15. INSERT INTO dbo.Coupon(Number) VALUES ( '10000003')
      16. INSERT INTO dbo.Coupon(Number) VALUES ( '10000004')
      17. INSERT INTO dbo.Coupon(Number) VALUES ( '10000005')
      18. INSERT INTO dbo.Coupon(Number) VALUES ( '10000006')
      19. --SELECT * FROM dbo.Coupon WITH(NOLOCK) --查詢數(shù)據(jù)
      20. --UPDATE Coupon SET [User]=NULL, UseTime=NULL, IsFlag=0 --還原數(shù)據(jù)
      21. -----------------1、模擬高并發(fā)普通更新-----------------
      22. DECLARE @User VARCHAR(50) --模擬要使用優(yōu)惠券的用戶
      23. DECLARE @TempId INT --模擬抽選出來的要使用的優(yōu)惠券
      24. SET @User='a'
      25. BEGIN TRAN
      26. SELECT @TempId=Id FROM dbo.Coupon WHERE IsFlag=0 --高并發(fā)時此語句有可能另外一個該事務(wù)已取出的Id
      27. --WAITFOR DELAY '00:00:05' --改用此方式要開兩個SQL Management客戶端
      28. UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
      29. COMMIT TRAN
      30. --ROLLBACK TRAN
      31. -----------------2、悲觀鎖解決方案-----------------
      32. DECLARE @User VARCHAR(50) --模擬要使用優(yōu)惠券的用戶
      33. DECLARE @TempId INT --模擬抽選出來的要使用的優(yōu)惠券
      34. SET @User='a'
      35. BEGIN TRAN
      36. SELECT @TempId=Id FROM dbo.Coupon WITH(UPDLOCK) WHERE IsFlag=0 --高并發(fā)時此語句會鎖定取出的Id數(shù)據(jù)行
      37. --WAITFOR DELAY '00:00:05' --改用此方式要開兩個SQL Management客戶端
      38. UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId
      39. COMMIT TRAN
      40. --ROLLBACK TRAN
      41. -----------------3、樂觀鎖解決方案-----------------
      42. ALTER TABLE dbo.Coupon ADD RowVer ROWVERSION NOT NULL --增加數(shù)據(jù)行版本戳類型字段(微軟新推薦數(shù)據(jù)字段,該字段每張表只能有一個,會在創(chuàng)建行或更新行時自動進行修改無需人為干涉,該字段不能建立索引及主鍵因為會頻繁修改)
      43. DECLARE @User VARCHAR(50) --模擬要使用優(yōu)惠券的用戶
      44. DECLARE @TempId INT --模擬抽選出來的要使用的優(yōu)惠券
      45. DECLARE @RowVer BINARY(8) --抽選出來的優(yōu)惠券的版本(ROWVERSION數(shù)據(jù)類型存儲大小為8字節(jié))
      46. SET @User='a'
      47. BEGIN TRY
      48. BEGIN TRAN
      49. SELECT @TempId=Id, @RowVer=RowVer FROM dbo.Coupon WHERE IsFlag=0 --取出可用的Id及對應(yīng)的版本戳
      50. --WAITFOR DELAY '00:00:05' --改用此方式要開兩個SQL Management客戶端
      51. UPDATE dbo.Coupon SET IsFlag=1, [User]=@User, UseTime=GETDATE() WHERE Id=@TempId AND RowVer=@RowVer
      52. IF(@@ROWCOUNT > 0)
      53. BEGIN
      54. PRINT('修改成功')
      55. COMMIT TRAN
      56. END
      57. ELSE
      58. BEGIN
      59. PRINT('該數(shù)據(jù)已被其他用戶修改')
      60. ROLLBACK TRAN
      61. END
      62. END TRY
      63. BEGIN CATCH
      64. ROLLBACK TRAN
      65. END CATCH
      66. --------------------------B客戶端連接 Lock(樂觀鎖)------------------------
      67. --此測試需要開兩個SQL Management Studio客戶端,在A客戶端使用WAITFOR DELAY來模擬并發(fā)占用,在B客戶端執(zhí)行與A客戶端相同的SQL腳本即可(注釋掉WAITFOR),所以在此不放相同代碼了。


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

      我認為如果同一張表的并發(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ù)讀和幻讀的詳細信息,請參閱并發(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)鎖行為的詳細信息,請參閱鎖兼容性(數(shù)據(jù)庫引擎)。

         3:  不能為通過插入、更新或刪除操作修改過的表指定 READUNCOMMITTED 和 NOLOCK。SQL Server 查詢優(yōu)化器忽略 FROM 子句中應(yīng)用于 UPDATE 或 DELETE 語句的目標表的 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有兩條記錄,我準備更新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)生嚴重問題的。關(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)址內(nèi)容:

      http://www.cnblogs.com/huangxincheng/p/4292320.html

      http://blog./13651903/viewspace-1091664/

      http://www.cnblogs.com/kerrycode/p/3946268.html

      http://www.cnblogs.com/taiyonghai/p/5674462.html

      --- end ---

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

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多