分析SIX鎖和鎖分區導致的死鎖

来源:http://www.cnblogs.com/Joe-T/archive/2016/06/12/5577883.html
-Advertisement-
Play Games

什麼是SIX鎖? 官方文檔鎖模式中說到: 意向排他共用 (SIX):保護針對層次結構中某些(而並非所有)低層資源請求或獲取的共用鎖以及針對某些(而並非所有)低層資源請求或獲取的意向排他鎖。 頂級資源允許使用併發 IS 鎖。 例如,獲取表上的 SIX 鎖也將獲取正在修改的頁上的意向排他鎖以及修改的行上 ...


什麼是SIX鎖?

官方文檔鎖模式中說到:

意向排他共用 (SIX):保護針對層次結構中某些(而並非所有)低層資源請求或獲取的共用鎖以及針對某些(而並非所有)低層資源請求或獲取的意向排他鎖。 頂級資源允許使用併發 IS 鎖。 例如,獲取表上的 SIX 鎖也將獲取正在修改的頁上的意向排他鎖以及修改的行上的排他鎖。 雖然每個資源在一段時間內只能有一個 SIX 鎖,以防止其他事務對資源進行更新,但是其他事務可以通過獲取表級的 IS 鎖來讀取層次結構中的低層資源。

官方說明比較晦澀難懂,我嘗試用一種易懂的方式說明SIX是什麼。

關於鎖有幾個概念:粒度、層次結構和鎖之間的相容性。鎖是用來鎖定資源,而資源是包括很多種的,而這些不同的資源代表著不同的粒度。不同的資源間存在著層次結構,如表、分區、頁、行、鍵等。鎖的類型用很多種,粗略的分類包括共用鎖(S)、更新鎖(U)、排他鎖(X)和架構鎖(Sch)等,而不同類型的鎖,有些是互斥的,有些是相容的。如共用鎖與其它類型的鎖相互相容,排他鎖與其它的鎖類型互斥。

SQL Server分配鎖時,會沿著層次結構,從表級別開始分配鎖,然後到最下層的行和鍵。在分配鎖時,上級的資源會被分配意向鎖(I),用來表示這個資源的下級某個資源已經被鎖定了。意向鎖也可以分為IS,IX,IU等類型。例如,更新表中某一行,需要在在行上分配X鎖,而在行所屬的數據頁中分配意向鎖IX,數據頁所屬的表上分配IX鎖。

如果一個會話的事務當前持有了某個表或者數據頁的S鎖,而它接下來又要去修改表中的某一個行。這種情況下,事務需要獲取行上的X鎖和表或數據頁上的IX鎖,但是SQL Server只允許一個會話在一個資源上獲取一個鎖。也就是說沒有辦法在已經獲得表或者頁級別的S鎖之後又分配IX給它。為瞭解決這個問題,於是就出現了兩者的結合體:S+IX=SIX。 同理,如果先持有IX,再去獲取S,也會得到SIX。

另外SQL Server中還有類似的鎖類型UIX(U+IX),SIU(S+IU),機理也是一樣的。這三種鎖被稱為轉換鎖。

 

什麼是鎖分區?

首先不要把鎖分區(Lock Partitioning)和分區鎖(Partition Lock)搞混了。

官方文檔鎖分區

對於大型電腦系統,在經常被引用的對象上放置的鎖可能會變成性能瓶頸,因為獲取和釋放鎖對內部鎖資源造成了爭用。鎖分區通過將單個鎖資源拆分為多個鎖資源而提高了鎖性能。此功能只適用於擁有 16 個或更多 CPU 的系統,它是自動啟用的,而且無法禁用。只有對象鎖可以分區

鎖任務訪問幾個共用資源,其中兩個通過鎖分區進行優化:

  • 調節鎖(Spinlock)。它控制對鎖資源(例如行或表)的訪問。

    不進行鎖分區,一個調節鎖就得管理單個鎖資源的所有鎖請求。在具有大量活動的系統上,在鎖請求等待釋放調節鎖時會出現資源爭用的現象。在這種情況下,獲取鎖可能變成了一個瓶頸,並且可能會對性能造成負面影響。

    為了減少對單個鎖資源的爭用,鎖分區將單個鎖資源拆分成多個鎖資源,以便將負荷分佈到多個調節鎖上。

  • 記憶體。它用於存儲鎖資源結構。

    獲取調節鎖後,鎖結構將存儲在記憶體中,然後即可對其進行訪問和可能的修改。將鎖訪問分佈到多個資源中有助於消除在 CPU 之間傳輸記憶體塊的需要,這有助於提高性能。

獲取已分區資源的鎖時:

  • 只能獲取單個分區的 NL、SCH-S、IS、IU 和 IX 鎖模式

  • 對於以分區 ID 0 開始並且按照分區 ID 順序排列的所有分區,必須獲取非 NL、SCH-S、IS、IU 和 IX 模式的共用鎖 (S)、排他鎖 (X) 和其他鎖。已分區資源的這些鎖將比相同模式中未分區資源的鎖占用更多的記憶體,因為每個分區都是一個有效的單獨鎖。記憶體的增加由分區數決定。Windows 性能監視器中 SQL Server 鎖計數器將顯示已分區和未分區鎖所使用記憶體信息。

啟動一個事務時,它將被分配給一個分區。對於此事務,可以分區的所有鎖請求都使用分配給該事務的分區。按照此方法,不同事務對相同對象的鎖資源的訪問被分佈到不同的分區中。

通過一個示例觀察一下SIX和鎖分區:

create  table t2 (
id int identity(1,1) ,
col1 int,
col2 int
)
go
insert into t2
values (floor(rand()*100),floor(rand()*100))
go 20

set transaction isolation level serializable
begin tran
insert into t2 
values (floor(rand()*100),floor(rand()*100))
select id from t2 
where @@ROWCOUNT>0 and id=SCOPE_IDENTITY()
SELECT resource_type, request_mode, resource_description,resource_lock_partition
FROM   sys.dm_tran_locks
WHERE  resource_type <> 'database' and request_session_id=@@SPID
rollback
e.g.

 

image

這個實例有24顆CPU,所以通過resource_lock_partition看到分區編號最到23了。因為SIX模式要獲取所有鎖分區,所以看到所有分區上都有SIX。

從圖中可以看出同一個事務中,不同的鎖資源可以使用不同的鎖分區

 

實際案例分析

最近在做性能review時發現某些實例的Ring Buffer中記錄了一些死鎖,其中一個如下:

image

會話113持有了對象上的IX,需要再申請SIX。說明它修改數據後要去查詢數。

會話79持有了對象上的SIX,需要再申請SIX。這個就有點奇怪了,需要再仔細看看xml格式的死鎖信息。

<deadlock>
    <victim-list>
        <victimProcess id="process8809b88"/>
    </victim-list>
    <process-list>
        <process id="process8809b88" taskpriority="0" logused="6844" waitresource="OBJECT: 6:1541580530:10 " waittime="967" ownerId="4638862771" transactionname="user_transaction" lasttranstarted="2016-06-06T16:45:14.617" XDES="0x8001d050" lockMode="SIX" schedulerid="1" kpid="41740" status="suspended" spid="113" sbid="2" ecid="0" priority="0" trancount="1" lastbatchstarted="2016-06-06T16:45:14.627" lastbatchcompleted="2016-06-06T16:45:14.627" clientapp=".Net SqlClient Data Provider" hostname="xxxx" hostpid="12552" loginname="xxx" isolationlevel="serializable (4)" xactid="4638862771" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
            <executionStack>
                <frame procname="" line="3" stmtstart="220" sqlhandle="0x0200000072705a08155b23d8949f622375a2f263ba0d9099"></frame>
            <frame procname="" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000"></frame>
    </executionStack>
    <inputbuf>
    (@0 bigint,@1 varchar(20))insert [dbo].[CustomerVerify]([CustomerId], [VerifyRegisterCode]) values (@0, @1) select [VerifyID] from [dbo].[CustomerVerify] where @@ROWCOUNT &gt; 0 and [VerifyID] = scope_identity()
</inputbuf>
</process>
<process id="process886c748" taskpriority="0" logused="7128" waitresource="OBJECT: 6:1541580530:0 " waittime="967" ownerId="4638862727" transactionname="user_transaction" lasttranstarted="2016-06-06T16:45:14.493" XDES="0xbe484e90" lockMode="SIX" schedulerid="11" kpid="35316" status="suspended" spid="79" sbid="2" ecid="0" priority="0" trancount="1" lastbatchstarted="2016-06-06T16:45:14.517" lastbatchcompleted="2016-06-06T16:45:14.517" clientapp=".Net SqlClient Data Provider" hostname="xxxxx" hostpid="29284" loginname="xxxxx" isolationlevel="serializable (4)" xactid="4638862727" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
    <frame procname="" line="3" stmtstart="220" sqlhandle="0x0200000072705a08155b23d8949f622375a2f263ba0d9099"></frame>
<frame procname="" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000"></frame>
</executionStack>
<inputbuf>
(@0 bigint,@1 varchar(20))insert [dbo].[CustomerVerify]([CustomerId], [VerifyRegisterCode]) values (@0, @1) select [VerifyID] from [dbo].[CustomerVerify] where @@ROWCOUNT &gt; 0 and [VerifyID] = scope_identity()
</inputbuf>
</process>
</process-list>
<resource-list>
<objectlock lockPartition="10" objid="1541580530" subresource="FULL" dbid="6" objectname="" id="lock77e3c1b00" mode="IX" associatedObjectId="1541580530">
<owner-list>
  <owner id="process886c748" mode="IX"/>
</owner-list>
<waiter-list>
<waiter id="process8809b88" mode="SIX" requestType="wait"/>
</waiter-list>
</objectlock>
<objectlock lockPartition="0" objid="1541580530" subresource="FULL" dbid="6" objectname="" id="lock628e080" mode="SIX" associatedObjectId="1541580530">
<owner-list>
<owner id="process8809b88" mode="SIX"/>
</owner-list>
<waiter-list>
<waiter id="process886c748" mode="SIX" requestType="wait"/>
</waiter-list>
</objectlock>
</resource-list>
</deadlock>
dl

 

概括一下:

1.兩者執行同樣的語句。插入一條數據,然後把剛纔插入的這條數據的自增ID取出來。堆表,無索引。

(@0 bigint,@1 varchar(20))insert [dbo].[CustomerVerify]([CustomerId], [VerifyRegisterCode]) values (@0, @1) select [VerifyID] from [dbo].[CustomerVerify] where @@ROWCOUNT &gt; 0 and [VerifyID] = scope_identity()

2. SPID 79持有鎖分區10上的IX,正在等待分配鎖分區0上的SIX.

    SPID 113持有鎖分區0上的SIX,正等待待分配鎖分區10上的SIX

3.會話的事務隔離級別都是可以序列化(isolationlevel="serializable (4)")。

基於以上,可以明白死鎖是怎麼發生的:

113在插入數據時持有某個鎖分區的IX,假設這個鎖分區為N,然後它要查詢剛纔插入的數據,所以轉換為SIX。SIX是需要分配所有鎖分區的,並且需要從第0鎖分區開始。分配到10分區時,發現10分區被與SIX不相容的IX鎖給鎖定了,陷入等待。

79插入數據時被分配了IX鎖,這個鎖分區為第10分區,然後查詢數據時需要將IX轉換為SIX。於是從第0鎖分區開始分配SIX,但是第0分區已經被113的SIX鎖定,並且SIX與SIX是不相容的,於是也陷入等待。

 

如何解決

總結前面的死鎖原因,問題就變成了:併發插入含有自增ID的堆表,並取出插入的自增ID,如何避免死鎖?

這種死鎖情況是非常非常罕見的。TF-1229可以禁用鎖分區的功能。個人覺得高併發的應用,與其禁用鎖分區來規避這種罕見情況,還不如設計好應用的重試機制。

有一點很奇怪,在上面的死鎖中,事務隔離級別是可序列化,而資料庫端是預設的已提交隔離級別。開發人員並沒設置連接會話的事務隔離級別,而這個會話的隔離級別卻改變了,這是什麼原因呢?

我的分析是,程式中使用了Entity Framework,而EF在6.0之前的預設連接隔離級別是可序列化。開發人員直接拿來用,也沒有註意到這種問題。

參考:

What is the default transaction isolation level in Entity Framework when I issue “SaveChanges()”?

Tips to avoid deadlocks in Entity Framework applications


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 由於從各光伏電站採集的數據量較大,必須解決海量數據的查詢、分析的問題。目前主要考慮兩種方式:1. Hadoop大數據技術;2. Oracle(數據倉庫)+BI; 本文僅介紹hadoop的技術要應用特征。 Hadoop 基本介紹 hadoop是一個平臺,是一個適合大數據的分散式存儲和計算的平臺。什麼是 ...
  • 控制文件是Oracle資料庫中一種非常重要的文件。 在Oracle資料庫中主要包括:數據文件、控制文件和重做日誌文件。在數據文件中存儲資料庫中的數據,包括各種資料庫對象及其數據。在重做日誌文件中存放用戶執行DML及DDL命令的記錄。 在控制文件中存放資料庫的結構信息。具體來說,在控制文件中包含以下重 ...
  • MicrosoftSQL Server 提供了三種複製類型。 每種複製類型都適合於不同應用程式的要求。 根據應用程式需要,可以在拓撲中使用一種或多種複製類型: 快照複製 事務複製 合併複製 為了幫助您選擇適當的複製類型,此主題提供了有關下列內容的信息: 複製方案 本部分簡要描述了複製的多種常用情況, ...
  • 1、 查詢Student表中的所有記錄的Sname、Ssex和Class列。 2. 查詢Student表的所有記錄。 3.查詢Score表中成績在60到80之間的所有記錄 4.查詢Score表中成績為85,86或88的記錄。 5.查詢Student表中“95031”班或性別為“女”的同學記錄。 6. ...
  • 在使用YourSQLDba做資料庫備份、維護時,像其它軟體一樣,版本升級是不可避免的。因為YourSQLDba一直在不停更新版本、擴展功能。下麵介紹一下升級YourSQLDba時的具體步驟和一些註意事項。下麵案例,YourSQLDba原版本為YourSQLDba version: 5.0.2 201... ...
  • 一、表訪問方式 CBO基礎概念中有講到,訪問表的方式有兩種:全表掃描和ROWID掃描。 全表掃描的執行計劃:TABLE ACCESS FULL ROWID掃描對應執行計劃:TABLE ACCESS BY USER ROWID 或 TABLE ACCESS BY INDEX ROWID 通過例子說明 ...
  • 轉載請附原文鏈接:http://www.cnblogs.com/wingsless/p/5578727.html 上一篇中我簡單的分析了一下InnoDB緩衝池LRU演算法的相關源碼,其實說不上是分析,應該是自己的筆記,不過我還是發揚大言不慚的精神寫成分析好了。在此之後,我繼續閱讀了Buf0rea.c文 ...
  • 一、DDL 1、DDL的概述 DDL(Data Definition Language 數據定義語言)用於操作對象和對象的屬性,這種對象包括資料庫本身,以及資料庫對象,像:表、視圖等等,DDL對這些對象和屬性的管理和定義具體表現在Create、Drop和Alter上。特別註意:DDL操作的“對象”的 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...