[轉帖]事務鎖定和行版本控制指南

  1. https://learn.microsoft.com/zh-cn/sql/relational-databases/sql-server-transaction-locking-and-row-versioning-guide?view=sql-server-ver16

     

適用於:SQL ServerAzure SQL 數據庫Azure SQL 託管實例Azure Synapse AnalyticsAnalytics Platform System (PDW)

在任意數據庫中,事務管理不善常常導致用戶很多的系統中出現爭用和性能問題。 隨着訪問數據的用戶數量的增加,擁有能夠高效地使用事務的應用程序也變得更爲重要。 本指南說明 SQL Server 數據庫引擎使用的鎖定和行版本控制機制,以確保每個事務的物理完整性並提供有關應用程序如何高效控制事務的信息。

 備註

優化鎖定是 2023 年引入的一項數據庫引擎功能,可大幅減少鎖內存和併發寫入時所需的鎖數量。 本文已更新,介紹了具有和不具有優化鎖定的 SQL Server 數據庫引擎。 目前,優化鎖定僅在 Azure SQL 數據庫中可用。

優化鎖定已大幅更新本文的某些部分,包括:

事務基本知識

事務是作爲單個邏輯工作單元執行的一系列操作。 一個邏輯工作單元必須有四個屬性,稱爲原子性、一致性、隔離性和持久性 (ACID) 屬性,只有這樣才能成爲一個事務。

原子性
事務必須是原子工作單元;對於其數據修改,要麼全都執行,要麼全都不執行。

一致性
事務在完成時,必須使所有的數據都保持一致狀態。 在相關數據庫中,所有規則都必須應用於事務的修改,以保持所有數據的完整性。 事務結束時,所有的內部數據結構(如 B 樹索引或雙向鏈接列表)都必須是正確的。

 備註

SQL Server 文檔在提到索引時一般使用 B 樹這個術語。 在行存儲索引中,SQL Server 實現了 B+ 樹。 這不適用於列存儲索引或內存中數據存儲。 有關詳細信息,請參閱 SQL Server 以及 Azure SQL 索引體系結構和設計指南

隔離
由併發事務所做的修改必須與任何其他併發事務所做的修改隔離。 事務識別數據時數據所處的狀態,要麼是另一併發事務修改它之前的狀態,要麼是第二個事務修改它之後的狀態,事務不會識別中間狀態的數據。 這稱爲可串行性,因爲它能夠重新裝載起始數據,並且重播一系列事務,以使數據結束時的狀態與原始事務執行的狀態相同。

持續性
完成完全持久的事務之後,它的影響將永久存在於系統中。 即使系統發生故障,修改也會保留。 SQL Server 2014 (12.x) 及更高版本啓用延遲的持久事務。 提交延遲的持久事務後,該事務日誌記錄將保留在磁盤上。 有關延遲事務持續性的詳細信息,請參閱文章控制事務持續性

SQL 程序員要負責啓動和結束事務,同時強制保持數據的邏輯一致性。 程序員必須定義數據修改的順序,使數據相對於其組織的業務規則保持一致。 程序員將這些修改語句包括到一個事務中,使 SQL Server 數據庫引擎能夠強制該事務的物理完整性。

企業數據庫系統(如 SQL Server 數據庫引擎的實例)有責任提供一種機制,保證每個事務的物理完整性。 SQL Server 數據庫引擎提供了:

  • 鎖定設備,使事務保持隔離。

  • 通過記錄設備,保證事務持久性。 對於完全持久的事務,在其提交之前,日誌記錄將強制寫入磁盤。 因此,即使服務器硬件、操作系統或 SQL Server 數據庫引擎的實例自身出現故障,該實例也可以在重新啓動時使用事務日誌,將所有未完成的事務自動地回滾到系統出現故障的點。 提交延遲的持久事務後,該事務日誌記錄將強制寫入磁盤。 如果在日誌記錄強制寫入磁盤前系統出現故障,此類事務可能會丟失。 有關延遲事務持續性的詳細信息,請參閱文章控制事務持續性

  • 事務管理特性,強制保持事務的原子性和一致性。 事務啓動之後,就必須成功完成(提交),否則 SQL Server 數據庫引擎將撤消該事務啓動之後對數據所做的所有修改。 此操作稱爲回滾事務,因爲它將數據恢復到那些更改發生前的狀態。

控制事務

應用程序主要通過指定事務啓動和結束的時間來控制事務。 可以使用 Transact-SQL 語句或數據庫應用程序編程接口 (API) 函數來指定這些時間。 系統還必須能夠正確處理那些在事務完成之前便終止事務的錯誤。 有關詳細信息,請參閱事務在 ODBC 中執行事務以及 SQL Server Native Client 中的事務

默認情況下,事務按連接級別進行管理。 在一個連接上啓動一個事務後,該事務結束之前,在該連接上執行的所有 Transact-SQL 語句都是該事務的一部分。 但是,在多重活動結果集 (MARS) 會話中,Transact-SQL 顯式或隱式事務將變成批範圍的事務,這種事務按批處理級別進行管理。 當批處理完成時,如果批範圍的事務還沒有提交或回滾,SQL Server 將自動回滾該事務。 有關詳細信息,請參閱使用多重活動結果集 (MARS)

啓動事務

使用 API 函數和 Transact-SQL 語句,可以在 SQL Server 數據庫引擎實例中將事務作爲顯式、自動提交或隱式事務來啓動。

顯式事務
顯式事務是指這樣的事務:你在其中通過 API 函數或發出 Transact-SQL BEGIN TRANSACTION、COMMIT TRANSACTION、COMMIT WORK、ROLLBACK TRANSACTION 或 ROLLBACK WORK Transact-SQL 語句明確定義事務的開始和結束。 當事務結束時,連接將返回到啓動顯式事務前所處的事務模式,或者是隱式模式,或者是自動提交模式。

你可以使用顯式事務中除以下語句之外的所有 Transact-SQL 語句:

  • CREATE DATABASE
  • ALTER DATABASE
  • DROP DATABASE
  • CREATE FULLTEXT CATALOG
  • ALTER FULLTEXT CATALOG
  • DROP FULLTEXT CATALOG
  • DROP FULLTEXT INDEX
  • ALTER FULLTEXT INDEX …
  • CREATE FULLTEXT INDEX …
  • BACKUP
  • RESTORE
  • RECONFIGURE
  • 全文系統存儲過程
  • sp_dboption 用於設置數據庫選項,或在顯式事務或隱式事務內部修改 master 數據庫的任何系統過程。

 備註

UPDATE STATISTICS 可在顯式事務內使用。 但是,UPDATE STATISTICS 提交獨立於封閉的事務,並且不能回滾。

自動提交事務
自動提交模式是 SQL Server 數據庫引擎的默認事務管理模式。 每個 Transact-SQL 語句在完成時,都被提交或回滾。 如果一個語句成功地完成,則提交該語句;如果遇到錯誤,則回滾該語句。 只要沒有顯式事務或隱性事務覆蓋自動提交模式,與 SQL Server 數據庫引擎實例的連接就以此默認模式操作。 自動提交模式也是 ADO、OLE DB、ODBC 和 DB 庫的默認模式。

隱式事務
當連接以隱式事務模式進行操作時,SQL Server 數據庫引擎實例將在提交或回滾當前事務後自動啓動新事務。 無須描述事務的開始,只需提交或回滾每個事務。 隱性事務模式生成連續的事務鏈。 通過 API 函數或 Transact-SQL SET IMPLICIT_TRANSACTIONS ON 語句,將隱性事務模式設置爲打開。 此模式也稱爲 Autocommit OFF,請參閱 setAutoCommit Method (SQLServerConnection)

爲連接將隱性事務模式設置爲打開之後,當 SQL Server 數據庫引擎的實例首次執行以下任何語句時,都會自動啓動一個事務:

  • ALTER TABLE

  • CREATE

  • DELETE

  • DROP

  • FETCH

  • GRANT

  • INSERT

  • OPEN

  • REVOKE

  • SELECT

  • TRUNCATE TABLE

  • UPDATE

  • 批處理級事務只能應用於多重活動結果集 (MARS),在 MARS 會話中啓動的 Transact-SQL 顯式或隱式事務變爲批處理級事務。 當批處理完成時沒有提交或回滾的批處理級事務自動由 SQL Server 進行回滾。

  • 分佈式事務分佈式事務跨越兩個或多個稱爲資源管理器的服務器。 稱爲事務管理器的服務器組件必須在資源管理器之間協調事務管理。 如果分佈式事務由 Microsoft 分佈式事務處理協調器 (MS DTC) 之類的事務管理器或其他支持 Open Group XA 分佈式事務處理規範的事務管理器來協調,則在這樣的分佈式事務中,每個 SQL Server 數據庫引擎的實例都可以作爲資源管理器來運行。 有關詳細信息,請參閱 MS DTC 文檔。

    跨越兩個或多個數據庫的 SQL Server 數據庫引擎的單個實例中的事務實際上是分佈式事務。 該實例對分佈式事務進行內部管理;對於用戶而言,其操作就像本地事務一樣。

    對於應用程序而言,管理分佈式事務很像管理本地事務。 當事務結束時,應用程序會請求提交或回滾事務。 不同的是,分佈式提交必須由事務管理器管理,以儘量避免出現因網絡故障而導致事務由某些資源管理器成功提交,但由另一些資源管理器回滾的情況。 通過分兩個階段(準備階段和提交階段)管理提交進程可避免這種情況,這稱爲兩階段提交 (2PC)。

    • 準備階段當事務管理器收到提交請求時,它會向該事務涉及的所有資源管理器發送準備命令。 然後,每個資源管理器將盡力使該事務持久,並且所有保存該事務日誌映像的緩衝區將被刷新到磁盤中。 當每個資源管理器完成準備階段時,它會向事務管理器返回準備成功或準備失敗的消息。 SQL Server 2014 (12.x) 引入了延遲事務持續性。 在提交延遲的持久事務後,該事務的日誌圖像將刷入磁盤。 有關延遲事務持續性的詳細信息,請參閱文章控制事務持續性

    • 提交階段如果事務管理器從所有資源管理器收到準備成功的消息,它將向每個資源管理器發送一個提交命令。 然後,資源管理器就可以完成提交。 如果所有資源管理器都報告提交成功,那麼事務管理器就會嚮應用程序發送一個成功通知。 如果任一資源管理器報告準備失敗,那麼事務管理器將向每個資源管理器發送一個回滾命令,並嚮應用程序表明提交失敗。

      SQL Server 數據庫引擎應用程序可以通過 Transact-SQL 或數據庫 API 來管理分佈式事務。 有關詳細信息,請參閱 BEGIN DISTRIBUTED TRANSACTION (Transact-SQL)

結束事務

你可以使用 COMMIT 或 ROLLBACK 語句,或者通過相應 API 函數來結束事務。

  • 提交如果事務成功,則提交。 COMMIT 語句保證事務的所有修改在數據庫中都永久有效。 COMMIT 語句還釋放事務使用的資源(例如,鎖)。

  • 回滾如果事務中出現錯誤,或用戶決定取消事務,則回滾該事務。 ROLLBACK 語句通過將數據返回到它在事務開始時所處的狀態,來取消事務中的所有修改。 ROLLBACK 還釋放事務佔用的資源。

 備註

在爲支持多個活動的結果集 (MARS) 而建立的連接中,只要還有待執行的請求,就無法提交通過 API 函數啓動的顯式事務。 如果在未完成的操作還在運行時嘗試提交此類事務,將導致出現錯誤。

事務處理過程中的錯誤

如果某個錯誤使事務無法成功完成,SQL Server 會自動回滾該事務,並釋放該事務佔用的所有資源。 如果客戶端與 SQL Server 數據庫引擎實例的網絡連接中斷了,則當網絡向實例通知該中斷後,該連接的任何未完成事務均會被回滾。 如果客戶端應用程序失敗或客戶端計算機崩潰或重新啓動,也會中斷連接,而且當網絡向 SQL Server 數據庫引擎的實例通知該中斷後,該實例會回滾所有未完成的連接。 如果客戶端從該應用程序註銷,任何未完成的事務均會被回滾。

如果批處理中出現運行時語句錯誤(如違反約束),則 SQL Server 數據庫引擎中的默認行爲是隻回滾產生該錯誤的語句。 可以使用 SET XACT_ABORT 語句更改此行爲。 在執行 SET XACT_ABORT ON 之後,任何運行時語句錯誤將導致當前事務的自動回滾。 編譯錯誤(如語法錯誤)不受 SET XACT_ABORT 的影響。 有關詳細信息,請參閱 SET XACT_ABORT (Transact-SQL)

出現錯誤時,糾正操作(COMMIT 或 ROLLBACK)應包括在應用程序代碼中。 處理錯誤(包括那些事務中的錯誤)的一種有效工具是 Transact-SQL TRY...CATCH 構造。 有關包括事務的示例的詳細信息,請參閱 TRY...CATCH (Transact-SQL)。 從 SQL Server 2012 (11.x) 開始,可使用 THROW 語句引發異常並將執行轉移到 CATCH 構造的 TRY...CATCH 塊。 有關詳細信息,請參閱 THROW (Transact-SQL)

自動提交模式下的編譯和運行時錯誤

在自動提交模式下,有時看起來好像 SQL Server 數據庫引擎的實例回滾了整個批處理而不是僅僅一個 SQL 語句。 當遇到的錯誤是編譯錯誤而非運行時錯誤時,會發生這種情況。 編譯錯誤會阻止 SQL Server 數據庫引擎生成執行計劃,這樣批處理中的任何語句都不會執行。 儘管看起來好像是回滾了產生錯誤的語句之前的所有語句,但該錯誤阻止了批處理中的所有語句的執行。 在下面的示例中,由於發生編譯錯誤,第三個批處理中的 INSERT 語句都沒有執行。 但看起來好像是前兩個 INSERT 語句沒有執行便進行了回滾。

SQL
CREATE TABLE TestBatch (Cola INT PRIMARY KEY, Colb CHAR(3));
GO
INSERT INTO TestBatch VALUES (1, 'aaa');
INSERT INTO TestBatch VALUES (2, 'bbb');
INSERT INTO TestBatch VALUSE (3, 'ccc');  -- Syntax error.
GO
SELECT * FROM TestBatch;  -- Returns no rows.
GO

在下面的示例中,第三個 INSERT 語句產生運行時重複主鍵錯誤。 由於前兩個 INSERT 語句成功地執行並且提交,因此它們在運行時錯誤之後被保留下來。

SQL
CREATE TABLE TestBatch (Cola INT PRIMARY KEY, Colb CHAR(3));
GO
INSERT INTO TestBatch VALUES (1, 'aaa');
INSERT INTO TestBatch VALUES (2, 'bbb');
INSERT INTO TestBatch VALUES (1, 'ccc');  -- Duplicate key error.
GO
SELECT * FROM TestBatch;  -- Returns rows 1 and 2.
GO

SQL Server 數據庫引擎使用延遲的名稱解析,直到執行時才解析對象名稱。 在下面的示例中,執行並提交了前兩個 INSERT 語句,在第三個 TestBatch 語句由於引用一個不存在的表而產生運行時錯誤之後,這兩行仍然保留在 INSERT 表中。

SQL
CREATE TABLE TestBatch (Cola INT PRIMARY KEY, Colb CHAR(3));
GO
INSERT INTO TestBatch VALUES (1, 'aaa');
INSERT INTO TestBatch VALUES (2, 'bbb');
INSERT INTO TestBch VALUES (3, 'ccc');  -- Table name error.
GO
SELECT * FROM TestBatch;  -- Returns rows 1 and 2.
GO

鎖定和行版本控制基本知識

當多個用戶同時訪問數據時,SQL Server 數據庫引擎使用以下機制確保事務的完整性和保持數據庫的一致性:

  • 鎖定

    每個事務對所依賴的資源(如行、頁或表)請求不同類型的鎖。 鎖可以阻止其他事務以某種可能會導致事務請求鎖出錯的方式修改資源。 當事務不再依賴鎖定的資源時,它將釋放鎖。

  • 行版本控制

    當啓用基於行版本控制的隔離級別時,SQL Server 數據庫引擎將維護修改的每一行的版本。 應用程序可以指定事務使用行版本查看事務或查詢開始時存在的數據,而不是使用鎖保護所有讀取。 通過使用行版本控制,讀取操作阻止其他事務的可能性將大大降低。

鎖定和行版本控制可以防止用戶讀取未提交的數據,還可以防止多個用戶嘗試同時更改同一數據。 如果不進行鎖定或行版本控制,對數據執行的查詢可能會返回數據庫中尚未提交的數據,從而產生意外的結果。

應用程序可以選擇事務隔離級別,爲事務定義保護級別,以防被其他事務所修改。 可以爲各個 Transact-SQL 語句指定表級別的提示,進一步定製行爲以滿足應用程序的要求。

管理併發數據訪問

同時訪問一種資源的用戶被視爲併發訪問資源。 併發數據訪問需要某些機制,以防止多個用戶試圖修改其他用戶正在使用的資源時產生負面影響。

併發影響

修改數據的用戶會影響同時讀取或修改相同數據的其他用戶。 即這些用戶可以併發訪問數據。 如果數據存儲系統沒有併發控制,則用戶可能會看到以下負面影響:

  • 丟失更新

    當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,會發生丟失更新問題。 每個事務都不知道其他事務的存在。 最後的更新將覆蓋由其他事務所做的更新,這將導致數據丟失。

    例如,兩個編輯人員製作了同一文檔的電子副本。 每個編輯人員獨立地更改其副本,然後保存更改後的副本,這樣就覆蓋了原始文檔。 最後保存其更改副本的編輯人員覆蓋另一個編輯人員所做的更改。 如果在一個編輯人員完成並提交事務之前,另一個編輯人員不能訪問同一文件,則可避免此問題。

  • 未提交的依賴關係(髒讀)

    當第二個事務選擇其他事務正在更新的行時,會發生未提交的依賴關係問題。 第二個事務正在讀取的數據還沒有提交併且可能由更新此行的事務所更改。

    例如,一個編輯人員正在更改電子文檔。 在更改過程中,另一個編輯人員複製了該文檔(該副本包含到目前爲止所做的全部更改)並將其分發給預期的用戶。 此後,第一個編輯人員認爲目前所做的更改是錯誤的,於是刪除了所做的編輯並保存了文檔。 分發給用戶的文檔包含不再存在的編輯內容,並且這些編輯內容應視爲從未存在過。 如果在第一個編輯人員保存最終更改並提交事務之前,任何人都不能讀取更改的文檔,則可以避免此問題。

  • 不一致的分析(不可重複讀)

    當第二個事務多次訪問同一行而且每次讀取不同的數據時,會發生不一致的分析問題。 不一致的分析與未提交的依賴關係類似,因爲其他事務也是正在更改第二個事務正在讀取的數據。 但是,在不一致的分析中,第二個事務讀取的數據是由已進行了更改的事務提交的。 此外,不一致的分析涉及多次(兩次或更多)讀取同一行,而且每次信息都被其他事務更改,因此我們稱之爲“不可重複讀”。

    例如,編輯人員兩次讀取同一文檔,但在兩次讀取之間,作者重寫了該文檔。 當編輯人員第二次讀取文檔時,文檔已更改。 原始讀取不可重複。 如果在編輯人員完成最後一次讀取文檔之前,作者不能更改文檔,則可以避免此問題。

  • 虛擬讀取

    執行兩個相同的查詢但第二個查詢返回的行集合是不同的,此時就會發生虛擬讀取。 下面的示例顯示了何時會出現幻讀。 假定下面兩個事務同時執行。 由於第二個事務中的 SELECT 語句更改了兩個事務所用的數據,所以第一個事務中的兩個 INSERT 語句可能返回不同的結果。

    SQL
    --Transaction 1
    BEGIN TRAN;
    SELECT ID FROM dbo.employee
    WHERE ID > 5 and ID < 10;
    --The INSERT statement from the second transaction occurs here.
    SELECT ID FROM dbo.employee
    WHERE ID > 5 and ID < 10;
    COMMIT;
    
    SQL
    --Transaction 2
    BEGIN TRAN;
    INSERT INTO dbo.employee
      (Id, Name) VALUES(6 ,'New');
    COMMIT;
    
  • 由於行更新導致讀取缺失和重複讀

    • 缺失一個更新行或多次看到某更新行

      在 READ UNCOMMITTED 級別運行的事務不會發出共享鎖來防止其他事務修改當前事務讀取的數據。 在 READ COMMITTED 級別運行的事務會發出共享鎖,但是在讀取行後會釋放行鎖或頁鎖。 無論哪種情況,在你掃描索引時,如果另一個用戶在你讀取期間更改行的索引鍵列,則在鍵更改將行移至你的掃描位置之前的位置時,該行可能會再次出現。 同樣,在鍵更改將行移至你已讀取的索引中的某位置時,該行將不會出現。 若要避免此問題,請使用 SERIALIZABLE 或 HOLDLOCK 提示或行版本控制。 有關詳細信息,請參閱表提示 (Transact-SQL)

    • 缺失非更新目標的一行或多行

      使用 READ UNCOMMITTED 時,如果使用分配順序掃描(使用 IAM 頁)查詢讀取行,當其他事務導致頁拆分時,可能會缺失行。 當使用已提交的讀取時不會發生這種情況,因爲在頁拆分期間將會保持表鎖;當表沒有聚集索引時也不會發生這種情況,因爲更新不會導致頁拆分。

併發類型

當許多人試圖同時修改數據庫中的數據時,必須實現一個控制系統,使一個人所做的修改不會對他人所做的修改產生負面影響。 這稱爲併發控制。

併發控制理論根據建立併發控制的方法而分爲兩類:

  • 悲觀併發控制

    一個鎖定系統,可以阻止用戶以影響其他用戶的方式修改數據。 如果用戶執行的操作導致應用了某個鎖,只有這個鎖的所有者釋放該鎖,其他用戶才能執行與該鎖衝突的操作。 這種方法之所以稱爲悲觀併發控制,是因爲它主要用於數據爭用激烈的環境中,以及發生併發衝突時用鎖保護數據的成本低於回滾事務的成本的環境中。

  • 樂觀併發控制

    在樂觀併發控制中,用戶讀取數據時不鎖定數據。 當一個用戶更新數據時,系統將進行檢查,查看該用戶讀取數據後其他用戶是否又更改了該數據。 如果其他用戶更新了數據,將產生一個錯誤。 一般情況下,收到錯誤信息的用戶將回滾事務並重新開始。 這種方法之所以稱爲樂觀併發控制,是由於它主要在以下環境中使用:數據爭用不大且偶爾回滾事務的成本低於讀取數據時鎖定數據的成本。

SQL Server 支持一定範圍的併發控制。 用戶通過爲遊標上的連接或併發選項選擇事務隔離級別來指定併發控制的類型。 這些特性可以使用 Transact-SQL 語句或通過數據庫應用程序編程接口(API,如 ADO、ADO.NET、OLE DB 和 ODBC)的屬性和特性來定義。

SQL Server 數據庫引擎中的隔離級別

事務指定一個隔離級別,該隔離級別定義一個事務必須與由其他事務進行的資源或數據更改相隔離的程度。 隔離級別從允許的併發副作用(例如,髒讀或虛擬讀取)的角度進行描述。

事務隔離級別控制:

  • 讀取數據時是否獲取鎖以及所請求的鎖類型。
  • 佔用讀取鎖的時間。
  • 引用其他事務修改的行的讀取操作是否:
    • 在該行上的排他鎖被釋放之前阻塞其他事務。
    • 檢索在啓動語句或事務時存在的行的已提交版本。
    • 讀取未提交的數據修改。

 重要

選擇事務隔離級別不影響爲保護數據修改而獲取的鎖。 事務總是在其修改的任何數據上獲取排他鎖並在事務完成之前持有該鎖,不管爲該事務設置了什麼樣的隔離級別。 對於讀取操作,事務隔離級別主要定義保護級別,以防受到其他事務所做更改的影響。

較低的隔離級別可以增強許多用戶同時訪問數據的能力,但也增加了用戶可能遇到的併發副作用(例如髒讀或丟失更新)的數量。 相反,較高的隔離級別減少了用戶可能遇到的併發副作用的類型,但需要更多的系統資源,並增加了一個事務阻塞其他事務的可能性。 應平衡應用程序的數據完整性要求與每個隔離級別的開銷,在此基礎上選擇相應的隔離級別。 最高隔離級別(可序列化)保證事務在每次重複讀取操作時都能準確檢索到相同的數據,但需要通過執行某種級別的鎖定來完成此操作,而鎖定可能會影響多用戶系統中的其他用戶。 最低隔離級別(未提交讀)可以檢索其他事務已經修改、但未提交的數據。 在未提交讀中,所有併發副作用都可能發生,但因爲沒有讀取鎖定或版本控制,所以開銷最少。

數據庫引擎隔離級別

ISO 標準定義了以下隔離級別,SQL Server 數據庫引擎支持所有這些隔離級別:

隔離級別定義
未提交的讀取 隔離事務的最低隔離級別,只能保證不讀取物理上損壞的數據。 在此級別上,允許髒讀,因此一個事務可能看見其他事務所做的尚未提交的更改。
已提交的讀取 允許事務讀取另一個事務以前讀取(未修改)的數據,而不必等待第一個事務完成。 SQL Server 數據庫引擎保留寫入鎖(在所選數據上獲取)直到事務結束,但是一執行 SELECT 操作就釋放讀取鎖。 這是 SQL Server 數據庫引擎默認級別。
可重複的讀取 SQL Server 數據庫引擎保留在所選數據上獲取的讀取鎖和寫入鎖,直到事務結束。 但是,因爲不管理範圍鎖,可能發生虛擬讀取。
可序列化 隔離事務的最高級別,事務之間完全隔離。 SQL Server 數據庫引擎保留在所選數據上獲取的讀取鎖和寫入鎖,在事務結束時釋放它們。 SELECT 操作使用分範圍的 WHERE 子句時獲取範圍鎖,主要爲了避免虛擬讀取。

注意:請求可序列化隔離級別時,複製的表上的 DDL 操作和事務可能失敗。 這是因爲複製查詢使用的提示可能與可序列化隔離級別不兼容。

SQL Server 還支持使用行版本控制的其他兩個事務隔離級別。 一個是 READ COMMITTED 隔離的實現,另一個是事務隔離級別(快照)。

行版本控制隔離級別定義
讀取已提交的快照 (RCSI) 當 READ_COMMITTED_SNAPSHOT 數據庫選項設置爲 ON 時,READ COMMITTED 隔離使用行版本控制提供語句級讀取一致性。 讀取操作只需要 SCH-S 表級別的鎖,不需要頁鎖或行鎖。 即,SQL Server 數據庫引擎使用行版本控制爲每個語句提供一個在事務上一致的數據快照,因爲該數據在語句開始時就存在。 不使用鎖來防止其他事務更新數據。 用戶定義的函數可以返回在包含 UDF 的語句開始後提交的數據。

如果 READ_COMMITTED_SNAPSHOT 數據庫選項設置爲 OFF(這是默認設置),當前事務運行讀取操作時,READ COMMITTED 隔離使用共享鎖來防止其他事務修改行。 共享鎖還會阻止語句在其他事務完成之前讀取由這些事務修改的行。 兩個實現都滿足 READ COMMITTED 隔離的 ISO 定義。
快照 快照隔離級別使用行版本控制來提供事務級別的讀取一致性。 讀取操作不獲取頁鎖或行鎖,只獲取 SCH-S 表鎖。 讀取其他事務修改的行時,讀取操作將檢索啓動事務時存在的行的版本。 當 ALLOW_SNAPSHOT_ISOLATION 數據庫選項設置爲 ON 時,只能對數據庫使用快照隔離。 默認情況下,用戶數據庫的此選項設置爲 OFF。

注意:SQL Server 不支持對元數據進行版本控制。 因此,對於在快照隔離下運行的顯式事務中可以執行的 DDL 操作存在限制。 在快照隔離下,BEGIN TRANSACTION 語句之後不允許使用任何公共語言運行時 (CLR) DDL 語句或下列 DDL 語句:ALTER TABLE、CREATE INDEX、CREATE XML INDEX、ALTER INDEX、DROP INDEX、DBCC REINDEX、ALTER PARTITION FUNCTION、ALTER PARTITION SCHEME。 在隱式事務中使用快照隔離時,允許使用這些語句。 根據定義,隱式事務爲單個語句,這使得它可以強制應用快照隔離的語義,即便使用 DDL 語句也是如此。 違反此原則會導致錯誤 3961: Snapshot isolation transaction failed in database '%.*ls' because the object accessed by the statement has been modified by a DDL statement in another concurrent transaction since the start of this transaction. It is not allowed because the metadata is not versioned. A concurrent update to metadata could lead to inconsistency if mixed with snapshot isolation.

下表顯示了不同隔離級別導致的併發副作用。

隔離級別髒讀不可重複讀虛擬讀取
未提交的讀取
已提交的讀取
可重複的讀取 No
快照 No
可序列化 No

有關每個事務隔離級別控制的特定類型的鎖定或行版本控制的詳細信息,請參閱 SET TRANSACTION ISOLATION LEVEL (Transact-SQL)

可以使用 Transact-SQL 或通過數據庫 API 來設置事務隔離級別。

Transact-SQL
Transact-SQL 腳本使用 SET TRANSACTION ISOLATION LEVEL 語句。

ADO
ADO 應用程序將 IsolationLevel 對象的 Connection 屬性設置爲 adXactReadUncommittedadXactReadCommittedadXactRepeatableRead 或 adXactReadSerializable

ADO.NET
使用 System.Data.SqlClient 託管命名空間的 ADO.NET 應用程序可以調用 SqlConnection.BeginTransaction 方法並將 IsolationLevel 選項設置爲 UnspecifiedChaosReadUncommittedReadCommittedRepeatableReadSerializable 或 Snapshot

OLE DB
啓動事務時,使用 OLE DB 的應用程序會調用 ITransactionLocal::StartTransactionisoLevel 設置爲 ISOLATIONLEVEL_READUNCOMMITTEDISOLATIONLEVEL_READCOMMITTEDISOLATIONLEVEL_REPEATABLEREADISOLATIONLEVEL_SNAPSHOT 或 ISOLATIONLEVEL_SERIALIZABLE

在自動提交模式下指定事務隔離級別時,OLE DB 應用程序可以將 DBPROPSET_SESSION 屬性 DBPROP_SESS_AUTOCOMMITISOLEVELS 設置爲 DBPROPVAL_TI_CHAOSDBPROPVAL_TI_READUNCOMMITTEDDBPROPVAL_TI_BROWSEDBPROPVAL_TI_CURSORSTABILITYDBPROPVAL_TI_READCOMMITTEDDBPROPVAL_TI_REPEATABLEREADDBPROPVAL_TI_SERIALIZABLEDBPROPVAL_TI_ISOLATED 或 DBPROPVAL_TI_SNAPSHOT

ODBC
ODBC 應用程序調用 SQLSetConnectAttrAttribute 設置爲 SQL_ATTR_TXN_ISOLATIONValuePtr 設置爲 SQL_TXN_READ_UNCOMMITTEDSQL_TXN_READ_COMMITTEDSQL_TXN_REPEATABLE_READ 或 SQL_TXN_SERIALIZABLE

對於快照事務,應用程序調用 SQLSetConnectAttr,屬性設置爲 SQL_COPT_SS_TXN_ISOLATIONValuePtr 設置爲 SQL_TXN_SS_SNAPSHOT。 你可以使用或 SQL_COPT_SS_TXN_ISOLATION 或 SQL_ATTR_TXN_ISOLATION 檢索快照事務。

數據庫引擎中的鎖定

鎖定是 SQL Server 數據庫引擎用來同步多個用戶同時對同一個數據塊的訪問的一種機制。

在事務獲取數據塊當前狀態的依賴關係(比如通過讀取或修改數據)之前,它必須保護自己不受其他事務對同一數據進行修改的影響。 事務通過請求鎖定數據塊來達到此目的。 鎖有多種模式,如共享或排他。 鎖模式定義了事務對數據所擁有的依賴關係級別。 如果某個事務已獲得特定數據的鎖,則其他事務不能獲得會與該鎖模式發生衝突的鎖。 如果事務請求的鎖模式與已授予同一數據的鎖發生衝突,則 SQL Server 數據庫引擎的實例將暫停事務請求直到第一個鎖釋放。

當事務修改某個數據塊時,它將持有保護所做修改的特定鎖直到事務結束。 事務持有(所獲取的用來保護讀取操作的)鎖的時間長度,取決於事務隔離級別設置以及是否已啓用優化鎖定

  • 在未啓用優化鎖定時,寫入所需的行鎖和頁鎖將一直持有到事務結束。

  • 啓用優化鎖定時,僅在事務持續時間內持有事務 ID (TID) 鎖。 在默認隔離級別下,事務在事務結束之前不會持有寫入所需的行鎖和頁鎖。 這減少了所需的鎖內存並減少了對鎖升級的需求。 此外,在啓用優化鎖定時,限定後鎖 (LAQ) 優化會在不獲取鎖定的情況下,對行的最新提交版本上的查詢謂詞進行評估,從而提高併發性。

一個事務持有的所有鎖都在事務完成(無論是提交還是回滾)時釋放。

應用程序一般不直接請求鎖。 鎖由的一個 SQL Server 數據庫引擎部件(稱爲“鎖管理器”)在內部管理。 當 SQL Server 數據庫引擎的實例處理 Transact-SQL 語句時,SQL Server 數據庫引擎查詢處理器會確定要訪問的資源。 查詢處理器根據訪問類型和事務隔離級別設置來確定保護每一資源所需的鎖的類型。 然後,查詢處理器將向鎖管理器請求適當的鎖。 如果與其他事務所持有的鎖不會發生衝突,鎖管理器將授予該鎖。

鎖粒度和層次結構

SQL Server 數據庫引擎具有多粒度鎖定,允許一個事務鎖定不同類型的資源。 爲了儘量減少鎖定的開銷,SQL Server 數據庫引擎會自動將資源鎖定在適合任務的級別。 鎖定在較小的粒度(例如行)可以提高併發度,但開銷較高,因爲如果鎖定了許多行,則需要持有更多的鎖。 鎖定在較大的粒度(例如表)會降低了併發度,因爲鎖定整個表限制了其他事務對錶中任意部分的訪問。 但其開銷較低,因爲需要維護的鎖較少。

SQL Server 數據庫引擎通常必須獲取多粒度級別上的鎖才能完整地保護資源。 這組多粒度級別上的鎖稱爲鎖層次結構。 例如,爲了完整地保護對索引的讀取,SQL Server 數據庫引擎的實例可能必須獲取行上的共享鎖以及頁和表上的意向共享鎖。

下表顯示了 SQL Server 數據庫引擎可以鎖定的資源。

資源說明
RID 用於鎖定堆中的單個行的行標識符。
索引中用於保護可序列化事務中的鍵範圍的行鎖。
PAGE 數據庫中的 8 KB 頁,例如數據頁或索引頁。
EXTENT 一組連續的八頁,例如數據頁或索引頁。
HoBT 1 堆或 B 樹。 用於保護沒有聚集索引的表中的 B 樹(索引)或堆數據頁的鎖。
TABLE 1 包括所有數據和索引的整個表。
文件 數據庫文件。
APPLICATION 應用程序專用的資源。
METADATA 元數據鎖。
ALLOCATION_UNIT 分配單元。
DATABASE 整個數據庫。
XACT 2 優化鎖定中使用的事務 ID (TID) 鎖定。 請參閱事務 ID (TID) 鎖定

1 使用 ALTER TABLE 的 LOCK_ESCALATION 選項會對 HoBT 和 TABLE 鎖帶來影響。

2 其他鎖定資源可用於 XACT 鎖定資源,請參閱優化鎖定的診斷附加功能

鎖模式

SQL Server 數據庫引擎使用不同的鎖模式鎖定資源,這些鎖模式確定了併發事務訪問資源的方式。

下表顯示了 SQL Server 數據庫引擎使用的資源鎖模式。

鎖模式說明
共享 (S) 用於不更改或不更新數據的讀取操作,如 SELECT 語句。
更新 (U) 用於可更新的資源中。 防止當多個會話在讀取、鎖定以及隨後可能進行的資源更新時發生常見形式的死鎖。
排他 (X) 用於數據修改操作,例如 INSERT、UPDATE 或 DELETE。 確保不會同時對同一資源進行多重更新。
意向 用於建立鎖的層次結構。 意向鎖包含三種類型:意向共享 (IS)、意向排他 (IX) 和意向排他共享 (SIX)。
架構 在執行依賴於表架構的操作時使用。 架構鎖包含兩種類型:架構修改 (Sch-M) 和架構穩定性 (Sch-S)。
大容量更新 (BU) 在將數據大容量複製到表中且指定了 TABLOCK 提示時使用。
鍵範圍 當使用可序列化事務隔離級別時保護查詢讀取的行的範圍。 確保再次運行查詢時其他事務無法插入符合可序列化事務的查詢的行。

共享鎖

共享鎖(S 鎖)允許併發事務在封閉式併發控制下讀取 (SELECT) 資源。 資源上存在共享鎖(S 鎖)時,任何其他事務都不能修改數據。 讀取操作一完成,就立即釋放資源上的共享鎖(S 鎖),除非將事務隔離級別設置爲可重複讀或更高級別,或者在事務持續時間內用鎖定提示保留共享鎖(S 鎖)。

更新鎖

數據庫引擎在準備執行更新時放置更新 (U) 鎖。 U 鎖與 S 鎖兼容,但一次只有一個事務可以在給定資源上持有 U 鎖。 這是關鍵 - 許多併發事務可以持有 S 鎖,但只有一個事務可以持有資源上的 U 鎖。 更新 (U) 鎖最終升級到獨佔鎖(X)以更新行。

當在查詢中指定 UPDLOCK 表提示時,不執行 UPDATE 的查詢也可以獲取更新 (U) 鎖。 應用程序通常使用“選擇行,然後更新行”模式,其中讀取和寫入在事務內顯式分隔。 在這種情況下,如果隔離級別可重複讀取或可序列化,併發更新可能會死鎖。 相反,應用程序可遵循“選擇帶有 UPDLOCK 提示的行,然後更新行”模式。

  • 在可重複讀或可序列化事務中,此事務讀取數據(獲取資源的共享 (S) 鎖),然後修改數據(此操作要求鎖轉換爲排他(X) 鎖)。 如果兩個事務獲得了資源上的共享 (S) 鎖,然後試圖同時更新數據,則一個事務會嘗試將鎖轉換爲排他 (X) 鎖。 共享到排他鎖的轉換必須等待一段時間,因爲一個事務的排他鎖與其他事務的共享 (S) 鎖不兼容;發生鎖等待。 第二個事務試圖獲取排他鎖(X 鎖)以進行更新。 由於兩個事務都要轉換爲排他 (X) 鎖,並且每個事務都等待另一個事務釋放共享 (S) 鎖,因此發生死鎖。

  • 在默認已提交讀隔離級別下,S 鎖持續時間較短,使用後立即釋放。 短持續時間鎖不太可能導致死鎖。

  • 如果在寫入中使用 UPDLOCK 提示,則事務必須有權訪問該行的最新版本。 如果最新版本不再可見,則預計能夠在使用 SNAPSHOT 隔離時接收 Msg 3960, Level 16, State 2 Snapshot isolation transaction aborted due to update conflict。 有關示例,請參閱使用快照隔離

排他鎖

排他鎖(X 鎖)可以防止併發事務對資源進行訪問。 使用排他鎖(X 鎖)時,任何其他事務都無法修改數據;僅在使用 NOLOCK 提示或未提交讀隔離級別時纔會進行讀取操作。

數據修改語句(如 INSERT、UPDATE 和 DELETE)合併了修改和讀取操作。 語句在執行所需的修改操作之前首先執行讀取操作以獲取數據。 因此,數據修改語句通常請求共享鎖和排他鎖。 例如,UPDATE 語句可能根據與一個表的聯接修改另一個表中的行。 在此情況下,除了請求更新行上的排他鎖之外,UPDATE 語句還將請求在聯接表中讀取的行上的共享鎖。

意向鎖

SQL Server 數據庫引擎使用意向鎖來確保共享 (S) 鎖或排他 (X) 鎖放置在鎖層次結構的底層資源上。 意向鎖之所以命名爲“意向鎖”,是因爲在較低級別鎖前可獲取它們,因此會通知意向將鎖放置在較低級別上。

意向鎖有兩種用途:

  • 防止其他事務以會使較低級別的鎖無效的方式修改較高級別資源。
  • 提高 SQL Server 數據庫引擎在較高的粒度級別檢測鎖衝突的效率。

例如,在該表的頁或行上請求共享鎖(S 鎖)之前,在表級請求共享意向鎖。 在表級設置意向鎖可防止另一個事務隨後在包含那一頁的表上獲取排他鎖(X 鎖)。 意向鎖可以提高性能,因爲 SQL Server 數據庫引擎僅在表級檢查意向鎖來確定事務是否可以安全地獲取該表上的鎖。 而不需要檢查表中的每行或每頁上的鎖以確定事務是否可以鎖定整個表。

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

鎖模式說明
意向共享 (IS) 保護針對層次結構中某些(而並非所有)低層資源請求或獲取的共享鎖。
意向排他 (IX) 保護針對層次結構中某些(而並非所有)低層資源請求或獲取的排他鎖。 IX 是 IS 的超集,它也保護針對低層級別資源請求的共享鎖。
意向排他共享 (SIX) 保護針對層次結構中某些(而並非所有)低層資源請求或獲取的共享鎖以及針對某些(而並非所有)低層資源請求或獲取的意向排他鎖。 頂級資源允許使用併發 IS 鎖。 例如,獲取表上的 SIX 鎖也將獲取正在修改的頁上的意向排他鎖以及修改的行上的排他鎖。 雖然每個資源在一段時間內只能有一個 SIX 鎖,以防止其他事務對資源進行更新,但是其他事務可以通過獲取表級的 IS 鎖來讀取層次結構中的低層資源。
意向更新 (IU) 保護針對層次結構中所有低層資源請求或獲取的更新鎖。 僅在頁資源上使用 IU 鎖。 如果進行了更新操作,IU 鎖將轉換爲 IX 鎖。
共享意向更新 (SIU) S 鎖和 IU 鎖的組合,作爲分別獲取這些鎖並且同時持有兩種鎖的結果。 例如,事務執行帶有 PAGLOCK 提示的查詢,然後執行更新操作。 帶有 PAGLOCK 提示的查詢將獲取 S 鎖,更新操作將獲取 IU 鎖。
更新意向排他 (UIX) U 鎖和 IX 鎖的組合,作爲分別獲取這些鎖並且同時持有兩種鎖的結果。

架構鎖

SQL Server 數據庫引擎在表數據定義語言 (DDL) 操作(例如添加列或刪除表)的過程中使用架構修改 (Sch-M) 鎖。 保持該鎖期間,Sch-M 鎖將阻止對錶進行併發訪問。 這意味着 Sch-M 鎖在釋放前將阻止所有外圍操作。

某些數據操作語言 (DML) 操作(例如表截斷)使用 Sch-M 鎖阻止併發操作訪問受影響的表。

SQL Server 數據庫引擎在編譯和執行查詢時使用架構穩定性 (Sch-S) 鎖。 Sch-S 鎖不會阻止某些事務鎖,其中包括排他 (X) 鎖。 因此,在編譯查詢的過程中,其他事務(包括那些針對表使用 X 鎖的事務)將繼續運行。 但是,無法針對表執行獲取 Sch-M 鎖的併發 DDL 操作和併發 DML 操作。

批量更新鎖

大容量更新鎖(BU 鎖)允許多個線程將數據併發地大容量加載到同一表,同時防止其他不進行大容量加載數據的進程訪問該表。 SQL Server 數據庫引擎在滿足以下兩個條件時,使用批量更新 (BU) 鎖。

  • 你使用 Transact-SQL BULK INSERT 語句或 OPENROWSET(BULK) 函數,或者你使用某個大容量插入 API 命令(如 .NET SqlBulkCopy)、OLEDB 快速加載 API 或 ODBC 大容量複製 API 來將數據大容量複製到表。
  • 指定了 TABLOCK 提示或使用 sp_tableoption 設置 table lock on bulk load 表選項。

 提示

與持有較少限制性大容量更新 (BU) 鎖的 BULK INSERT 語句不同,具有 TABLOCK 提示的 INSERT INTO...SELECT 語句持有一個針對表的意向排他 (X) 鎖。 也就是說你不能使用並行插入操作插入行。

鍵範圍鎖

在使用可序列化事務隔離級別時,對於 Transact-SQL 語句讀取的記錄集,鍵範圍鎖可以隱式保護該記錄集中包含的行範圍。 鍵範圍鎖可防止虛擬讀取。 通過保護行之間鍵的範圍,它還防止對事務訪問的記錄集進行虛擬插入或刪除。

鎖兼容性

鎖兼容性控制多個事務能否同時獲取同一資源上的鎖。 如果資源已被另一事務鎖定,則僅當請求鎖的模式與現有鎖的模式相兼容時,纔會授予新的鎖請求。 如果請求鎖的模式與現有鎖的模式不兼容,則請求新鎖的事務將等待釋放現有鎖或等待鎖超時間隔過期。 例如,沒有與排他鎖兼容的鎖模式。 如果具有排他鎖(X 鎖),則在釋放排他鎖(X 鎖)之前,其他事務均無法獲取該資源的任何類型(共享、更新或排他)的鎖。 另一種情況是,如果共享鎖(S 鎖)已應用到資源,則即使第一個事務尚未完成,其他事務也可以獲取該項的共享鎖或更新鎖(U 鎖)。 但是,在釋放共享鎖之前,其他事務無法獲取排他鎖。

下表顯示了最常見的鎖模式的兼容性。

現有的授予模式ISSUIXSIXX
請求的模式            
意向共享 (IS)
共享 (S) No
更新 (U) No No
意向排他 (IX) No
意向排他共享 (SIX) No No No
排他 (X) No No No No

 備註

意向排他鎖(IX 鎖)與 IX 鎖模式兼容,因爲 IX 表示打算只更新部分行而不是所有行。 還允許其他事務嘗試讀取或更新部分行,只要這些行不是其他事務當前更新的行即可。 此外,如果兩個事務嘗試更新同一行,將在表級和頁級上授予這兩個事務 IX 鎖。 但是,將在行級授予一個事務 X 鎖。 另一個事務必須在該行級鎖被刪除前等待。

使用下表可以確定 SQL Server 中所有可用的鎖模式的兼容性。

A table showing a matrix of lock conflicts and compatibility.

鍵範圍鎖

在使用可序列化事務隔離級別時,對於 Transact-SQL 語句讀取的記錄集,鍵範圍鎖可以隱式保護該記錄集中包含的行範圍。 可序列化隔離級別要求每當在事務期間執行任一查詢時,該查詢都必須獲取相同的行集。 鍵範圍鎖可防止其他事務插入其鍵值位於可序列化事務讀取的鍵值範圍內的新行,從而確保滿足此要求。

鍵範圍鎖可防止虛擬讀取。 通過保護行之間的鍵範圍,它還可以防止對事務訪問的記錄集進行虛擬插入。

鍵範圍鎖放置在索引上,指定開始鍵值和結束鍵值。 此鎖將阻止任何要插入、更新或刪除任何帶有該範圍內的鍵值的行的嘗試,因爲這些操作會首先獲取索引上的鎖。 例如,可序列化事務可以發出 SELECT 語句,該語句讀取鍵值與條件 BETWEEN 'AAA' AND 'CZZ' 匹配的所有行。 從“AAA”到“CZZ”範圍內的鍵值上的鍵範圍鎖可阻止其他事務插入帶有該範圍內的鍵值(例如“ADG”、“BBD”或“CAL”)的行。

鍵範圍鎖模式

鍵範圍鎖包括按範圍-行格式指定的範圍組件和行組件:

  • 範圍表示保護兩個連續索引項之間的範圍的鎖模式。
  • 行表示保護索引項的鎖模式。
  • 模式表示使用的組合鎖模式。 鍵範圍鎖模式由兩部分組成。 第一部分表示用於鎖定索引範圍 (RangeT) 的鎖類型,第二部分表示用於鎖定特定鍵 (K) 的鎖類型。 這兩部分用連字符 (-) 連接,例如 RangeT-K。
範圍“模式”說明
RangeS S RangeS-S 共享範圍,共享資源鎖;可序列化範圍掃描。
RangeS U RangeS-U 共享範圍,更新資源鎖;可串行更新掃描。
RangeI Null RangeI-N 插入範圍,空資源鎖;用於在索引中插入新鍵之前測試範圍。
RangeX X RangeX-X 排他範圍,排他資源鎖;用於更新範圍中的鍵。

 備註

內部空鎖模式與所有其他鎖模式兼容。

鍵範圍鎖模式有一個兼容性矩陣,表示哪些鎖與在重疊鍵和範圍上獲取的其他鎖兼容。

現有的授予模式SUXRangeS-SRangeS-URangeI-NRangeX-X
請求的模式              
共享 (S) No
更新 (U) No No
排他 (X) No No No No
RangeS-S No
RangeS-U No No
RangeI-N No
RangeX-X No No No No No

轉換鎖

當鍵範圍鎖與其他鎖重疊時,將創建轉換鎖。

鎖定 1鎖 2轉換鎖
S RangeI-N RangeI-S
U RangeI-N RangeI-U
X RangeI-N RangeI-X
RangeI-N RangeS-S RangeX-S
RangeI-N RangeS-U RangeX-U

在不同的複雜環境下(有時是在運行併發進程時),可以在一小段時間內觀察到轉換鎖。

可序列化範圍掃描、單獨提取、刪除和插入

鍵範圍鎖定確保以下操作是可序列化的:

  • 範圍掃描查詢
  • 對不存在的行的單獨提取
  • 刪除操作
  • 插入操作

必須滿足下列條件才能發生鍵範圍鎖定:

  • 事務隔離級別必須設置爲 SERIALIZABLE。
  • 查詢處理器必須使用索引來實現範圍篩選謂詞。 例如,SELECT 語句中的 WHERE 子句可以用以下謂詞建立範圍條件:ColumnX BETWEEN N**'AAA'** AND N**'CZZ'**。 僅當 ColumnX 被索引鍵覆蓋時,才能獲取鍵範圍鎖。

示例

以下表和索引用作隨後的鍵範圍鎖定示例的基礎。

A diagram of a sample of a Btree.

範圍掃描查詢

爲了確保範圍掃描查詢是可序列化的,每次在同一事務中執行的相同查詢應返回同樣的結果。 其他事務不能在範圍掃描查詢中插入新行;否則這些插入將成爲虛擬插入。 例如,以下查詢將使用上圖中的表和索引:

SQL
SELECT name
FROM mytable
WHERE name BETWEEN 'A' AND 'C';

鍵範圍鎖放置在與數據行範圍(名稱在值 Adam 與 Dale 之間的行)對應的索引項上,以防止添加或刪除滿足上述查詢條件的新行。 儘管此範圍中的第一個名稱是 Adam,但是此索引項的 RangeS-S 模式鍵範圍鎖確保了以字母 A 開頭的新名稱(例如 Abigail)不能添加在 Adam 之前。 同樣,Dale 索引項的 RangeS-S 鍵範圍鎖確保了以字母 C 開頭的新名稱(例如 Clive)不能添加在 Carlos 之後。

 備註

包含的 RangeS-S 鎖數量爲 n+1,此處 n 是滿足查詢條件的行數。

對不存在的數據的單獨提取

如果事務中的查詢試圖選擇不存在的行,則以後在相同的事務中發出這一查詢時,必須返回相同的結果。 不允許其他事務插入不存在的行。 例如,對於下面的查詢:

SQL
SELECT name
FROM mytable
WHERE name = 'Bill';

鍵範圍鎖放置在與從 Ben 到 Bing 的名稱範圍對應的索引項上,因爲名稱 Bill 將插入到這兩個相鄰的索引項之間。 RangeS-S 模式鍵範圍鎖放置在索引項 Bing 上。 這樣可阻止其他任何事務在索引項 Bill 與 Ben 之間插入值(例如 Bing)。

刪除操作,無優化鎖定

在事務中刪除值時,在事務執行刪除操作期間不必鎖定該值所屬的範圍。 鎖定刪除的鍵值直至事務結束足以保持可序列化性。 例如,對於下面的 DELETE 語句:

SQL
DELETE mytable
WHERE name = 'Bob';

排他鎖(X 鎖)放置在與名稱 Bob 對應的索引項上。 其他事務可以在刪除的值 Bob 的前後插入或刪除值。 但是任何試圖讀取、插入或刪除值 Bob 的事務都將被阻塞,直到刪除的事務提交或回滾爲止。 (READ_COMMITTED_SNAPSHOT 數據庫選項和快照隔離級別還允許從之前提交狀態的行版本中讀取。)

可以使用三個基本鎖模式執行範圍刪除:行鎖、頁鎖或表鎖。 行、頁或表鎖定策略由查詢優化器確定,也可以由用戶通過查詢優化器提示(例如 ROWLOCK、PAGLOCK 或 TABLOCK)來指定。 當使用 PAGLOCK 或 TABLOCK 時,如果從某個索引頁中刪除所有行,SQL Server 數據庫引擎將立即釋放該索引頁。 相反,當使用 ROWLOCK 時,所有刪除的行只是標記爲已刪除;以後通過後臺任務從索引頁中刪除它們。

具有優化鎖定的刪除操作

在刪除事務內的值時,會增量獲取和釋放行鎖和頁鎖,且在事務持續時間內不會持有行鎖和頁鎖。 例如,對於下面的 DELETE 語句:

SQL
DELETE mytable
WHERE name = 'Bob';

在事務持續時間內,所有修改的行上都放置 TID 鎖。 在與名稱 Bob 對應的索引項的 TID 上獲取鎖。 使用優化鎖定,更新時將繼續獲取頁鎖和行鎖,但每行更新後,每個頁鎖和行鎖都會釋放。 TID 鎖可保護行在事務完成之前不被更新。 任何試圖讀取、插入或刪除值 Bob 的事務都將被阻止,直到刪除的事務提交或回滾爲止。 (READ_COMMITTED_SNAPSHOT 數據庫選項和快照隔離級別還允許從之前提交狀態的行版本中讀取。)

否則,刪除操作的鎖定機制與未優化鎖定相同。

沒有優化鎖定的插入操作

在事務中插入值時,在事務執行插入操作期間不必鎖定該值所屬的範圍。 鎖定插入的鍵值直至事務結束足以維護可序列化性。 例如,對於下面的 INSERT 語句:

SQL
INSERT mytable VALUES ('Dan');

RangeI-N 模式鍵範圍鎖放置在與名稱 David 對應的索引項上,以測試範圍。 如果已授權鎖,則插入 Dan,並且排他鎖(X 鎖)將放置在值 Dan 上。 RangeI-N 模式鍵範圍鎖僅對測試範圍是必需的,而不在執行插入操作的事務期間保留。 其他事務可以在插入的值 Dan 的前後插入或刪除值。 但是,任何試圖讀取、插入或刪除值 Dan 的事務都將被阻塞,直到插入的事務提交或回滾爲止。

具有優化鎖定的插入操作

在事務中插入值時,在事務執行插入操作期間不必鎖定該值所屬的範圍。 很少獲取行鎖和頁鎖,僅當正在進行在線索引重新生成時,或者實例中存在可序列化事務時。 如果獲取行鎖和頁鎖,則會在事務持續時間內快速釋放這些鎖,並且不會持有。 將排他 TID 鎖放在插入的鍵值上,直至事務結束足以維護可序列化。 例如,對於下面的 INSERT 語句:

SQL
INSERT mytable VALUES ('Dan');

通過優化鎖定,僅當實例中至少有一個使用 SERIALIZABLE 隔離級別的事務時,纔會獲取 RangeI-N 鎖。 RangeI-N 模式鍵範圍鎖放置在與名稱 David 對應的索引項上,以測試範圍。 如果已授權鎖,則插入 Dan,並且排他鎖(X 鎖)將放置在值 Dan 上。 RangeI-N 模式鍵範圍鎖僅對測試範圍是必需的,而不在執行插入操作的事務期間保留。 其他事務可以在插入的值 Dan 的前後插入或刪除值。 但是,任何試圖讀取、插入或刪除值 Dan 的事務都將被阻塞,直到插入的事務提交或回滾爲止。

鎖升級

鎖升級是將許多細粒度鎖轉換爲較少粗粒度鎖的過程,從而減少系統開銷,同時增加併發爭用的可能性。

鎖升級的行爲有所不同,具體取決於是否啓用優化鎖定

無優化鎖定的鎖升級

當 SQL Server 數據庫引擎獲取低級別的鎖時,它還將在包含更低級別對象的對象上放置意向鎖:

  • 當鎖定行或索引鍵範圍時,數據庫引擎將在包含這些行或鍵的頁上放置意向鎖。
  • 當鎖定頁時,數據庫引擎將在包含這些頁的更高級別對象上放置意向鎖。 除了對象上的意向鎖外,還會對以下對象請求意向頁鎖:
    • 非聚集索引的葉級別頁
    • 聚集索引的數據頁
    • 堆數據頁

數據庫引擎可以爲同一語句執行行鎖定和頁鎖定,以最大程度地減少鎖的數量,並降低需要進行鎖升級的可能性。 例如,數據庫引擎可以在非聚集索引上放置頁鎖(如果在索引節點中選擇了足夠的連續鍵來滿足查詢),而在數據上放置行鎖。

升級鎖時,數據庫引擎會嘗試將表的意向鎖改爲對應的全鎖,例如,將意向排他 (IX) 鎖改爲排他 (X) 鎖,或將意向共享 (IS) 鎖改爲共享 (S) 鎖。 如果鎖升級嘗試成功且獲取了全表鎖,則堆或索引上事務持有的所有堆或 B 樹、頁 (PAGE) 或行級別 (RID) 鎖都會釋放。 如果無法獲取全鎖,當時不會發生鎖升級,而數據庫引擎將繼續獲取行、鍵或頁鎖。

數據庫引擎不會將行鎖或鍵範圍鎖升級到頁鎖,而是將它們直接升級到表鎖。 同樣,頁鎖始終升級爲表鎖。 對於關聯的分區,已分區表的鎖定可以升級到 HoBT 級別,而不是表鎖。 HoBT 級別鎖不一定鎖定分區的對齊 HoBT。

 備註

HoBT 級別鎖通常會增加併發性,但當鎖定不同分區的事務都將排他鎖擴展到其他分區時,可能會造成死鎖。 在極少數情況下,表鎖粒度的性能可能會更好。

如果由於併發事務所持有的鎖衝突而導致鎖升級嘗試失敗,則 數據庫引擎將對事務獲取的其他 1,250 個鎖重試鎖升級。

每個升級事件主要在單個 Transact-SQL 語句級別操作。 當事件啓動時,只要活動語句滿足升級閾值的要求,Transact-SQL 就會嘗試升級當前事務在活動語句所引用的任何表中持有的所有鎖。 如果在語句訪問表之前啓動升級事件,則不會嘗試升級該表上的鎖。 如果鎖升級成功,且當前語句引用了該表並且該表包含在升級事件中,則該事務在上一個語句中獲取並在事件開始時仍持有的任何鎖都會升級。

例如,假設會話執行以下操作:

  • 開始一個事務。
  • 更新 TableA。 這會在 TableA 中生成排他行鎖,這些鎖將一直持有到事務完成爲止。
  • 更新 TableB。 這會在 TableB 中生成排他行鎖,這些鎖將一直持有到事務完成爲止。
  • 執行聯接 TableA 與 TableC 的 SELECT。 查詢執行計劃要求先從 TableA 中檢索行,然後才從 TableC 中檢索行。
  • SELECT 語句在從 TableA 中檢索行時(此時還沒有訪問 TableC)觸發鎖升級。

如果鎖升級成功,只有會話在 TableA 中持有的鎖纔會升級。 這包括 SELECT 語句中的共享鎖和上一個 UPDATE 語句中的排他鎖。 由於決定是否應進行鎖升級時只考慮會話的 SELECT 語句在 TableA 中獲取的鎖,所以一旦升級成功,會話在 TableA 中持有的所有鎖都將被升級到該表的排他鎖,而 TableA 的所有其他較低粒度的鎖(包括意向鎖)都將被釋放。

不會嘗試升級 TableB 的鎖,因爲 SELECT 語句中沒有 TableB 的活動引用。 同樣,也不會嘗試升級 TableC 上尚未升級的鎖,因爲發生升級時尚未訪問過該表。

具有優化鎖的鎖升級

優化鎖定有助於減少鎖內存,因爲在事務持續時間內持有的鎖很少。 當 SQL Server 數據庫引擎獲取行鎖和頁鎖時,鎖升級也會發生類似的情況,但頻率要低得多。 優化鎖定通常可以成功避免鎖升級、減少所需的鎖數量和鎖內存量。

啓用優化鎖定,並在默認 READ COMMITTED 隔離級別中時,數據庫引擎在寫入完成後會立即釋放行鎖和頁鎖。 除了單個事務 ID (TID) 鎖除以外,在事務持續時間內不會持有行鎖和頁鎖。 這減少了鎖升級的可能性。

鎖升級閾值

如果沒有使用 ALTER TABLE SET LOCK_ESCALATION 選項來禁用表的鎖升級並且滿足以下任一條件,則將觸發鎖升級:

  • 單個 Transact-SQL 語句在單個未分區表或索引上獲取至少 5,000 個鎖。
  • 單個 Transact-SQL 語句在已分區表的單個分區上獲取至少 5,000 個鎖,並且 ALTER TABLE SET LOCK_ESCALATION 選項設爲 AUTO。
  • 數據庫引擎實例中的鎖數量超出了內存或配置閾值。

如果由於鎖衝突導致無法升級鎖,則每當獲取 1,250 個新鎖時,數據庫引擎便會觸發鎖升級。

Transact-SQL 語句的升級閾值

當數據庫引擎在每 1,250 個新獲取的鎖上檢查可能的升級時,並且當且僅當 Transact-SQL 語句已在表的單個引用上獲得至少 5,000 個鎖時,纔會進行鎖升級。 當 Transact-SQL 語句在表的單個引用上獲得至少 5,000 個鎖時,將觸發鎖升級。 例如,如果語句在一個索引中獲取 3,000 個鎖,而在同一個表的另一個索引中又獲取 3,000 個鎖,則不會觸發鎖升級。 同樣,如果語句對錶具有自聯接,且對錶的每個引用僅獲取表中的 3,000 個鎖,則不會觸發鎖升級。

鎖升級僅發生在觸發升級時已訪問的表上。 假定某個 SELECT 語句是一個按 TableATableB 和 TableC 順序訪問三個表的聯接。 該語句在 TableA 的聚集索引中獲取 3,000 個行鎖,在 TableB 的聚集索引中獲取至少 5,000 個行鎖,但是尚未訪問 TableC。 當數據庫引擎檢測到該語句已在 TableB 中獲取至少 5,000 個行鎖時,會嘗試升級當前事務在 TableB 中持有的所有鎖。 它還會嘗試升級當前事務在 TableA 中持有的所有鎖,但是由於 TableA 中鎖的數量小於 5,000,因此,升級無法成功。 但它不會嘗試在 TableC 中進行鎖升級,因爲發生升級時尚未訪問該表。

數據庫引擎實例的升級閾值

每當鎖的數量大於鎖升級的內存閾值時,數據庫引擎都會觸發鎖升級。 內存閾值取決於鎖配置選項的設置:

  • 如果“鎖”選項設置爲默認值 0,當鎖對象使用的內存是數據庫引擎所使用內存的 24%(不包括 AWE 內存)時,將達到鎖升級閾值。 用於表示鎖的數據結構長度大約爲 100 個字節。 該閾值是動態的,因爲數據庫引擎動態地獲得和釋放內存,以針對變化的工作負載進行調整。

  • 如果“鎖”選項設置爲非 0 值,則鎖升級閾值是“所”選項值的 40%(或者更低,如果存在內存壓力)。

數據庫引擎可以爲升級選擇任何會話中的活動語句,而且,只要實例中使用的鎖內存保持在閾值之上,每獲取 1,250 個新鎖,它就會爲升級選擇語句。

升級混合鎖類型

在發生鎖升級時,爲堆或索引選擇的鎖足夠強大,以滿足限制性最嚴格的較低級別鎖的要求。

例如,假設會話:

  • 開始一個事務。
  • 更新包含聚集索引的表。
  • 發出引用同一個表的 SELECT 語句。

UPDATE 語句會獲取以下鎖:

  • 更新的數據行上的排他 (X) 鎖。
  • 包含這些行的聚集索引頁上的意向排他(IX) 鎖。
  • 聚集索引上的 IX 鎖和表上的另一個鎖。

SELECT 語句會獲取以下鎖:

  • 所讀取的所有數據行上的共享 (S) 鎖,除非該行已受 UPDATE 語句中的 X 鎖保護。
  • 包含這些行的所有聚集索引頁上的意向共享鎖,除非該頁已受 IX 鎖保護。
  • 聚集索引或表上沒有鎖,因爲它們已受 IX 鎖保護。

如果 SELECT 語句獲取足夠的鎖來觸發鎖升級且升級成功,則表上的 IX 鎖將轉換爲 X 鎖,並且所有行鎖、頁鎖和索引鎖都將釋放。 更新和讀取均受表上的 X 鎖保護。

減少鎖定和升級

在大多數情況下,數據庫引擎在使用默認的鎖定和鎖升級設置進行操作時提供最佳性能。

如果數據庫引擎實例生成大量鎖並且頻繁進行鎖升級,請考慮使用以下策略減少鎖定數量:

  • 對於讀取操作,使用不會生成共享鎖的隔離級別:

    • READ COMMITTED 隔離級別(在 READ_COMMITTED_SNAPSHOT 數據庫選項設置爲 ON 時)。

    • 快照隔離級別。

    • READ UNCOMMITTED 隔離級別。 這隻能用於可以進行髒讀操作的系統。

       備註

      更改隔離級別會影響數據庫引擎實例上的所有表。

  • 使用 PAGLOCK 或 TABLOCK 表提示,使數據庫引擎使用頁、堆或索引鎖而不是低級別鎖。 但是,使用此選項會增加用戶阻止其他用戶嘗試訪問相同數據的問題,因此不應在具有多個併發用戶的系統中使用此選項。

  • 在未啓用優化鎖定時,對於已分區表,使用 ALTER TABLE 的 LOCK_ESCALATION 選項將鎖升級到 HoBT 級別而不是表級別,或者禁用鎖升級。

  • 將大批操作分成多個小批操作。 例如,假設你運行以下查詢以從審覈表中刪除幾十萬條舊記錄,然後發現它導致了會阻止其他用戶的鎖升級:

    SQL
    DELETE FROM LogMessages WHERE LogDate < '2/1/2002'
    

    通過一次刪除幾百條記錄,可以顯著減少每個事務累積的鎖數量,並防止鎖升級。 例如:

    SQL
    SET ROWCOUNT 500
    delete_more:
      DELETE FROM LogMessages WHERE LogDate < '2/1/2002'
    IF @@ROWCOUNT > 0 GOTO delete_more
    SET ROWCOUNT 0
    
  • 通過儘量提高查詢的效率,減少查詢的鎖佔用時間。 大型掃描或大量的書籤查找可能會增加鎖升級的機率;此外,它還會增加死鎖的可能性,並且通常會對併發和性能產生不利影響。 找到導致鎖升級的查詢後,尋找機會創建新索引,或向現有索引添加列以刪除索引或表掃描,並最大程度地提高索引查找的效率。 請考慮使用數據庫引擎優化顧問對查詢執行自動索引分析。 有關詳細信息,請參閱教程:數據庫引擎優化顧問。 此優化的一個目標是使索引查找儘可能少地返回行,以最大程度地降低書籤查找的成本(最大程度地提高特定查詢的索引選擇性)。 如果數據庫引擎估計書籤查找邏輯運算符可能返回多個行,則它可能使用 PREFETCH 來執行書籤查找。 如果數據庫引擎使用 PREFETCH 進行書籤查找,則它必須將部分查詢的事務隔離級別提升至可重複讀取部分查詢。 這意味着,在已提交讀隔離級別看起來類似於 SELECT 語句的內容可能會獲得數千個鍵鎖(在兩個聚集索引和一個非聚集索引上),這會導致此類查詢超出鎖升級閾值。 如果你發現已升級的鎖是共享表鎖(但該鎖在默認的已提交讀取隔離級別並不常見),則這一點特別重要。

    如果書籤查找 WITH PREFETCH 子句導致升級,請考慮向索引查找中出現的非聚集索引,或查詢計劃中書籤查找邏輯運算符下方的索引掃描邏輯運算符添加其他列。 可以創建覆蓋索引(一種索引,可包含查詢所使用表中的全部列),也可以至少創建一個包含聯接條件或 WHERE 子句所使用列的索引(如果無法在選擇列列表中包含所有列)。 嵌套循環聯接也可能使用 PREFETCH,並且這會導致相同的鎖定行爲。

  • 如果其他 SPID 當前持有不兼容的表鎖,則不會發生鎖升級。 鎖升級始終升級至表鎖,而不是頁鎖。 此外,如果由於另一個 SPID 持有不兼容的 TAB 鎖而導致鎖升級嘗試失敗,則嘗試升級的查詢在等待 TAB 鎖時不會被阻止。 相反,它會繼續在其原始、更細化的級別(行、鍵或頁)獲取鎖,並定期進行其他升級嘗試。 因此,阻止特定表發生鎖升級的一種方法是獲取並持有與已升級鎖類型不兼容的其他連接的鎖。 表級的 IX(意向排他)鎖不會鎖定任何行或頁,但仍與已升級的 S(共享)或 X(排他)TAB 鎖不兼容。 例如,假設你需要運行一個批處理作業,該作業會修改 mytable 表中的大量行,並且導致了由鎖升級造成的阻止行爲。 如果此作業始終在不到一個小時內完成,你可以創建一個包含以下代碼的 Transact-SQL 作業,並將此新作業計劃爲在批處理作業開始時間之前的幾分鐘啓動:

    SQL
    BEGIN TRAN
    SELECT * FROM mytable WITH (UPDLOCK, HOLDLOCK) WHERE 1=0
    WAITFOR DELAY '1:00:00'
    COMMIT TRAN
    

    此查詢將獲取 mytable 表的 IX 鎖,並持有該鎖一個小時,這會阻止在此時間段內該表發生鎖升級。 此批處理作業不會修改任何數據或阻止其他查詢(除非另一個查詢使用 TABLOCK 提示強制執行表鎖,或者管理員已使用 sp_indexoption 存儲過程禁用了頁鎖或行鎖)。

  • 你還可以使用跟蹤標誌 1211 和 1224 禁用所有或某些鎖升級。 不過,這些跟蹤標誌會對整個數據庫引擎全局禁用所有鎖升級。 鎖升級在數據庫引擎中非常有用,它最大程度地提高了查詢效率(該效率會因爲獲取和釋放數千個鎖產生的開銷而降低)。 此外,鎖升級還可以幫助最大程度地減少跟蹤鎖所需的內存。 數據庫引擎可以爲鎖結構動態分配的內存是有限的,因此,如果禁用鎖升級並且鎖內存增長到足夠大,則嘗試爲任何查詢分配額外的鎖可能會失敗並出現以下錯誤:Error: 1204, Severity: 19, State: 1 The SQL Server cannot obtain a LOCK resource at this time. Rerun your statement when there are fewer active users or ask the system administrator to check the SQL Server lock and memory configuration.

     備註

    在出現 MSSQLSERVER_1204 錯誤時,它將停止處理當前語句並導致活動事務回滾。 如果重啓數據庫服務,則回滾本身可能會阻止用戶或導致較長的數據庫恢復時間。

     備註

    使用鎖提示(如 ROWLOCK)只會更改初始鎖計劃。 鎖提示不會阻止鎖升級。

監視鎖升級

使用 lock_escalation 擴展事件 (xEvent) 來監視鎖升級,如以下示例中所示:

SQL
-- Session creates a histogram of the number of lock escalations per database
CREATE EVENT SESSION [Track_lock_escalation] ON SERVER
ADD EVENT sqlserver.lock_escalation(SET collect_database_name=(1),collect_statement=(1)
    ACTION(sqlserver.database_id,sqlserver.database_name,sqlserver.query_hash_signed,sqlserver.query_plan_hash_signed,sqlserver.sql_text,sqlserver.    username))
ADD TARGET package0.histogram(SET source=N'sqlserver.database_id')
GO

 重要

應使用 lock_escalation 擴展事件 (xEvent),而不是 SQL 跟蹤或 SQL Profiler 中的 Lock:Escalation 事件類。

動態鎖定

使用低級鎖(如行鎖)可以降低兩個事務同時在相同數據塊上請求鎖的可能性,從而提高併發性。 使用低級鎖還會增加鎖的數量以及管理鎖所需的資源。 使用高級表鎖或頁鎖可以減少開銷,但代價是降低了併發性。

A graph of locking cost vs. concurrency cost.

SQL Server 數據庫引擎使用動態鎖定策略確定最經濟的鎖。 執行查詢時,SQL Server 數據庫引擎會根據架構和查詢的特點自動決定最合適的鎖。 例如,爲了縮減鎖定的開銷,優化器可能在執行索引掃描時在索引中選擇頁級鎖。

動態鎖定具有下列優點:

  • 簡化數據庫管理。 數據庫管理員不必調整鎖升級閾值。
  • 提高了性能。 SQL Server 數據庫引擎通過使用適合任務的鎖使系統開銷減至最小。
  • 應用程序開發人員可以集中精力進行開發。 SQL Server 數據庫引擎會自動調整鎖定。

自 SQL Server 2008 (10.0.x) 起,鎖升級的行爲已更改,其中引入了 LOCK_ESCALATION 選項。 有關詳細信息,請參閱 ALTER TABLE 的 LOCK_ESCALATION 選項。

鎖分區

對於大型計算機系統,在經常被引用的對象上放置的鎖可能會變成性能瓶頸,因爲獲取和釋放鎖對內部鎖資源造成了爭用。 鎖分區通過將單個鎖資源拆分爲多個鎖資源而提高了鎖性能。 此功能只適用於擁有 16 個或更多 CPU 的系統,它是自動啓用的,而且無法禁用。 只有對象鎖可以分區。 具有子類型的對象鎖不會被分區。 有關詳細信息,請參閱 sys.dm_tran_locks (Transact-SQL)

瞭解鎖分區

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

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

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

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

  • 內存。 它用於存儲鎖資源結構。

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

實現和監視鎖分區

默認情況下,對於具有 16 個或更多 CPU 的系統,鎖分區是打開的。 啓用鎖分區後,將在 SQL Server 錯誤日誌中記錄一條信息性消息。

獲取已分區資源的鎖時:

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

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

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

sys.dm_tran_locks 動態管理視圖中的 resource_lock_partition 列爲鎖分區資源提供鎖分區 ID。 有關詳細信息,請參閱 sys.dm_tran_locks (Transact-SQL)

使用鎖分區

以下代碼示例說明了鎖分區。 在這些示例中,爲了顯示一個具有 16 個 CPU 的計算機系統上的鎖分區行爲,在兩個不同的會話中執行了兩個事務。

這些 Transact-SQL 語句創建了後續示例中使用的測試對象。

SQL
-- Create a test table.
CREATE TABLE TestTable  (col1 int);
GO

-- Create a clustered index on the table.
CREATE CLUSTERED INDEX ci_TestTable
    ON TestTable (col1);
GO

-- Populate the table.
INSERT INTO TestTable VALUES (1);
GO

示例 A

會話 1:

在一個事務中執行一個 SELECT 語句。 由於 HOLDLOCK 鎖提示的原因,此語句將獲取並保留一個對此表的意向共享鎖(IS 鎖)(此例中忽略行鎖和頁鎖)。 IS 鎖只能在分配給事務的分區中獲取。 對於此示例,假定 IS 鎖是在 ID 爲 7 的分區中獲取的。

SQL
-- Start a transaction.
BEGIN TRANSACTION
    -- This SELECT statement will acquire an IS lock on the table.
    SELECT col1
    FROM TestTable
    WITH (HOLDLOCK);

會話 2:

啓動事務,在此事務下運行 SELECT 語句將獲取共享鎖(S 鎖)並將其保留在表中。 將獲取所有分區的 S 鎖,這將產生多個表鎖,每個分區一個。 例如,在具有 16 個 cpu 的系統上,將對鎖分區 ID 爲 0-15 的鎖分區發出 16 個 S 鎖。 由於 S 鎖與分區 ID 7 上由會話 1 中的事務持有的 IS 鎖兼容,因此事務之間沒有阻塞。

SQL
BEGIN TRANSACTION
    SELECT col1
    FROM TestTable
    WITH (TABLOCK, HOLDLOCK);

會話 1:

將在會話 1 下仍處於活動狀態的事務下執行以下 SELECT 語句。 由於排他 (X) 表鎖提示,因此事務將嘗試獲取該表的 X 鎖。 但是,由會話 2 中的事務持有的 S 鎖將阻塞分區 ID 0 的 X 鎖。

SQL
SELECT col1
FROM TestTable
WITH (TABLOCKX);

示例 B

會話 1:

在一個事務中執行一個 SELECT 語句。 由於 HOLDLOCK 鎖提示的原因,此語句將獲取並保留一個對此表的意向共享鎖(IS 鎖)(此例中忽略行鎖和頁鎖)。 IS 鎖只能在分配給事務的分區中獲取。 對於此示例,假定 IS 鎖是在 ID 爲 6 的分區中獲取的。

SQL
-- Start a transaction.
BEGIN TRANSACTION
    -- This SELECT statement will acquire an IS lock on the table.
    SELECT col1
    FROM TestTable
    WITH (HOLDLOCK);

會話 2:

在一個事務中執行一個 SELECT 語句。 由於 TABLOCKX 鎖提示,事務將嘗試獲取表的排他鎖(X 鎖)。 請記住,必須獲取從分區 ID 0 開始的所有分區的 X 鎖。 將獲取所有分區 ID 0-5 的 X 鎖,但它會被爲分區 ID 6 獲取的 IS 鎖阻塞。

對於尚未獲取 X 鎖的分區 ID 7-15,其他事務可以繼續獲取鎖。

SQL
BEGIN TRANSACTION
    SELECT col1
    FROM TestTable
    WITH (TABLOCKX, HOLDLOCK);

SQL Server 數據庫引擎中基於行版本控制的隔離級別

從 SQL Server 2005 (9.x) 開始,SQL Server 數據庫引擎提供現有事務隔離級別(已提交讀)的實現,該實現使用行版本控制提供語句級快照。 SQL Server 數據庫引擎還提供一個事務隔離級別(快照),該級別也使用行版本控制提供事務級快照。

行版本控制是 SQL Server 中的一般框架,它在修改或刪除行時調用寫入時複製機制。 這要求在運行事務時,行的舊版本必須可供需要早先事務一致狀態的事務使用。 行版本控制可用於執行以下操作:

  • 在觸發器中生成插入的和刪除的表。 對任何由觸發器修改的行都將生成副本。 這包括由啓動觸發器的語句修改的行,以及由觸發器進行的任何數據修改。
  • 支持多個活動的結果集 (MARS)。 如果 MARS 會話在存在活動結果集的情況下發出一條數據修改語句(例如 INSERTUPDATE 或 DELETE),受修改語句影響的行將進行版本控制。
  • 支持指定 ONLINE 選項的索引操作。
  • 支持基於行版本控制的事務隔離級別:
    • 新實現的 READ COMMITTED 隔離級別,使用行版本控制提供語句級的讀取一致性。
    • 新快照隔離級別,提供事務級的讀取一致性。

tempdb 數據庫必須具有足夠的空間用於版本存儲區。 在 tempdb 已滿的情況下,更新操作將停止生成版本,並繼續執行,但是因爲所需的特定行版本不再存在,讀取操作可能會失敗。 這將影響諸如觸發器、MARS 和聯機索引的操作。

已提交讀和快照事務的行版本控制的使用過程分爲兩個步驟:

  1. 將 READ_COMMITTED_SNAPSHOT 和 ALLOW_SNAPSHOT_ISOLATION 數據庫選項之一或兩者設置爲 ON。

  2. 在應用程序中設置相應的事務隔離級別:

    • 當 READ_COMMITTED_SNAPSHOT 數據庫選項設置爲 ON 時,設置 READ COMMITTED 隔離級別的事務使用行版本控制。
    • 當 ALLOW_SNAPSHOT_ISOLATION 數據庫選項設置爲 ON 時,事務可以設置快照隔離級別。

當 READ_COMMITTED_SNAPSHOT 或 ALLOW_SNAPSHOT_ISOLATION 數據庫選項設置爲 ON 時,SQL Server 數據庫引擎向使用行版本控制操作數據的每個事務分配一個事務序列號 (XSN)。 事務在執行 BEGIN TRANSACTION 語句時啓動。 但是,事務序列號在執行 BEGIN TRANSACTION 語句後的第一次讀/寫操作時開始增加。 事務序列號在每次分配時都增加 1。

當 READ_COMMITTED_SNAPSHOT 或 ALLOW_SNAPSHOT_ISOLATION 數據庫選項設置爲 ON 時,將維護數據庫中執行的所有數據修改的邏輯副本(版本)。 特定的事務每次修改行時,SQL Server 數據庫引擎 實例都存儲以前提交的 tempdb 中行的圖像版本。 每個版本都標記有進行此更改的事務的事務序列號。 已修改行的版本使用鏈接列表鏈接在一起。 最新的行值始終存儲在當前數據庫中,並與 tempdb 中存儲的版本控制行鏈接在一起。

 備註

修改大型對象 (LOB) 時,只有已更改的片段纔會複製到 tempdb 中的版本存儲區。

行版本將保持足夠長的時間,以滿足在基於行版本控制的隔離級別下運行的事務的要求。 SQL Server 數據庫引擎跟蹤最早的可用事務序列號,並定期刪除帶有比最早使用的可用序列號更低的事務序列號的所有行版本。

兩個數據庫選項都設置爲 OFF 時,只對由觸發器或 MARS 會話修改的行或由聯機索引操作讀取的行生成副本。 這些行版本將在不再需要時被釋放。 後臺線程會定期執行來刪除陳舊的行版本。

 備註

對於短期運行的事務,已修改行的版本將可能緩存在緩衝池中,而不會寫入 tempdb 數據庫的磁盤文件中。 如果只是臨時需要副本行,它將只是簡單地從緩衝池中刪除而不會引發 I/O 開銷。

讀取數據時的行爲

當在基於行版本控制的隔離下運行的事務讀取數據時,讀取操作不會獲取正被讀取的數據上的共享鎖(S 鎖),因此不會阻塞正在修改數據的事務。 同時,由於減少了所獲取的鎖的數量,因此最大程度地降低了鎖定資源的開銷。 使用行版本控制的已提交讀隔離和快照隔離旨在提供副本數據的語句級或事務級讀取一致性。

所有查詢,包括在基於行版本控制的隔離級別下運行的事務,都在編譯和執行期間獲取 Sch-S(架構穩定性)鎖。 因此,當併發事務持有表的 Sch-M(架構修改)鎖時,將阻塞查詢。 例如,數據定義語言 (DDL) 操作在修改表的架構信息之前獲取 Sch-M 鎖。 查詢事務,包括在基於行版本控制的隔離級別下運行的事務,都會在嘗試獲取 Sch-S 鎖時被阻塞。 相反,持有 Sch-S 鎖的查詢將阻塞嘗試獲取 Sch-M 鎖的併發事務。

當使用快照隔離級別的事務啓動時,SQL Server 數據庫引擎實例將記錄所有當前活動的事務。 當快照事務讀取具有版本鏈的行時,SQL Server 數據庫引擎按照該鏈檢索行,其事務序列號爲:

  • 最接近但低於讀取行的快照事務序列號。

  • 不在快照事務啓動時活動的事務列表中。

由快照事務執行的讀取操作將檢索在快照事務啓動時已提交的每行的最新版本。 這提供了在事務啓動時存在的數據的事務一致快照。

使用行版本控制的已提交讀事務以大致相同的方式運行。 不同之處在於選擇行版本時,已提交讀取事務不使用其自身的事務序列號。 每次啓動語句時,已提交讀事務將讀取爲該 SQL Server 數據庫引擎實例發出的最新事務序列號。 這是用於爲該語句選擇正確的行版本的事務序列號。 這使已提交讀事務可以查看每個語句啓動時存在的數據的快照。

 備註

即使使用行版本控制的已提交讀事務提供了在語句級別上事務一致的數據視圖,但此類事務生成或訪問的行版本還將保留,直到事務完成時爲止。

修改數據時的行爲

無論是否存在優化鎖定,數據寫入的行爲都有明顯不同。

在沒有優化鎖定的情況下修改數據

在使用行版本控制的已提交讀事務中,使用阻塞性掃描(其中讀取數據值時將在數據行上獲取更新 (U) 鎖完成選擇要更新的行。 這與不使用行版本控制的已提交讀取事務相同。 如果數據行不符合更新標準,將釋放更新鎖並且將鎖定下一行並對其進行掃描。

在快照隔離下運行的事務對數據修改採用樂觀方法:獲取數據上的鎖後,才執行修改以強制應用約束。 否則,直到數據修改時才獲取數據上的鎖。 當數據行符合更新標準時,快照事務將驗證未被併發事務(在快照事務開始後提交)修改的數據行。 如果數據行已在快照事務以外修改,則將出現更新衝突,同時快照事務也將終止。 更新衝突由 SQL Server 數據庫引擎處理,無法禁用更新衝突檢測。

 備註

當快照事務訪問以下任意項目時,在快照隔離下運行的更新操作將在 READ COMMITTED 隔離下內部執行:

具有 FOREIGN KEY 約束的表。

在其他表的 FOREIGN KEY 約束中引用的表。

引用多個表的索引視圖。

但是,即使是在這些條件下,更新操作仍將繼續驗證數據是否未經其他事務修改。 如果數據已被其他事務修改,則快照事務將遭遇更新衝突並終止。 更新衝突必須由應用程序手動處理和重試。

在優化鎖定的情況下修改數據

啓用優化鎖定及啓用 READ_COMMITTED_SNAPSHOT (RCSI) 數據庫選項,並使用默認 READ COMMITTED 隔離級別,讀取器不會獲取任何鎖,而編寫器獲取短持續時間的低級別鎖,而不是在事務結束時過期的鎖。

建議啓用 RCSI,以通過優化鎖定獲取最高效率。 如果使用更嚴格的隔離級別(如可重複讀取或可序列化),數據庫引擎將強制爲讀取器和編寫器持有行鎖和頁鎖,直到事務結束,從而導致阻塞和鎖內存增加。

啓用 RCSI 並使用默認 READ COMMITTED 隔離級別時,編寫器根據行的最新提交版本按謂詞限定行,而無需獲取 U 鎖。 只有當行限定並且該行或頁上存在活動的寫入事務時,查詢纔會等待。 根據最新提交的版本進行限定,僅鎖定限定行可減少阻塞並提高併發性。

如果使用 RCSI 和默認 READ COMMITTED 隔離級別檢測到更新衝突,則會自動處理並重試這些衝突,而不會對客戶工作負載產生任何影響。

啓用優化鎖定並使用快照隔離級別後,更新衝突的行爲是相同的。 更新衝突必須由應用程序手動處理和重試。

 備註

有關優化鎖定的限定後鎖定 (LAQ) 功能的行爲更改的詳細信息,請參閱使用優化鎖定和 RCSI 更改查詢行爲

行爲摘要

下表概括了使用行版本控制的快照隔離與 READ COMMITTED 隔離之間的差異。

properties使用行版本控制的已提交讀隔離級別快照隔離級別
必須設置爲 ON 以便啓用所需支持的數據庫選項。 READ_COMMITTED_SNAPSHOT ALLOW_SNAPSHOT_ISOLATION
會話如何請求特定類型的行版本控制。 使用默認的已提交讀隔離級別,或運行 SET TRANSACTION ISOLATION LEVEL 語句來指定 READ COMMITTED 隔離級別。 這可以在事務啓動後完成。 需要執行 SET TRANSACTION ISOLATION LEVEL 來在事務啓動前指定 SNAPSHOT 隔離級別。
由語句讀取的數據的版本。 在每條語句啓動前提交的所有數據。 在每個事務啓動前提交的所有數據。
如何處理更新。 沒有優化鎖定:從行版本恢復到實際的數據,以選擇要更新的行並使用選擇的數據行上的更新鎖。 獲取要修改的實際數據行上的排他鎖。 沒有更新衝突檢測。

具有優化鎖定:根據上次提交的版本選擇行,無需獲取任何鎖。 如果行符合更新條件,則獲取排他行鎖或頁鎖。 如果檢測到更新衝突,則自動處理和重試更新衝突。
使用行版本選擇要更新的行。 嘗試獲取要修改的實際數據行上的排他鎖,如果數據已被其他事務修改,則出現更新衝突,同時快照事務也將終止。
更新衝突檢測 沒有優化鎖定:無。

具有優化鎖定:如果檢測到更新衝突,則自動處理和重試更新衝突。
集成支持。 無法禁用。

行版本控制資源的使用情況

行版本控制框架支持 SQL Server 中提供的以下功能:

  • 觸發器
  • 多個活動的結果集 (MARS)
  • 聯機索引

另外,行版本控制框架還支持下列基於行版本控制的事務隔離級別(默認情況下禁用):

  • READ_COMMITTED_SNAPSHOT 數據庫選項爲 ON 時,READ_COMMITTED 事務使用行版本控制提供語句級讀取一致性。
  • ALLOW_SNAPSHOT_ISOLATION 數據庫選項爲 ON 時,SNAPSHOT 事務使用行版本控制提供事務級讀取一致性。

基於行版本控制的隔離級別通過消除對讀取操作使用共享鎖來減少事務獲取的鎖數目。 這樣就減少了管理鎖所用資源,從而提高了系統性能。 另外還減少了其他事務獲取的鎖阻塞事務的次數,也就提高了性能。

基於行版本控制的隔離級別增加了數據修改所需的資源。 啓用這些選項會導致要複製數據庫中要修改的所有數據。 即使沒有使用基於行版本控制隔離的活動事務,也將修改前的數據副本存儲在 tempdb 中。 修改後的數據包括一個指向存儲在 tempdb 中的經過版本控制數據的指針。 對於大型對象,只將對象中更改過的部分複製到 tempdb 中。

tempdb 中使用的空間

對於每個 SQL Server 數據庫引擎實例,tempdb 都必須具有足夠的空間以容納在該實例中爲每個數據庫生成的行版本。 數據庫管理員必須確保 tempdb 具有足夠的空間來支持版本存儲區。 tempdb 中有以下兩種版本存儲區:

  • 聯機索引生成版本存儲區,用於所有數據庫中的聯機索引生成操作。
  • 公共版本存儲區,用於所有數據庫中的所有其他數據修改操作。

只要活動事務需要訪問行版本,就必須存儲行版本。 後臺線程每隔一分鐘刪除一次不再需要的行版本,從而釋放 tempdb 中的版本空間。 如果長時間運行的事務符合下列任何一個條件,則會阻止釋放版本存儲區中的空間:

  • 使用基於行版本控制的隔離。
  • 使用觸發器、MARS 或聯機索引生成操作。
  • 生成行版本。

 備註

在事務內部調用了觸發器後,即使觸發器完成後不再需要行版本,由觸發器創建的行版本將仍然受到維護直到事務結束。 這也同樣適用於使用行版本控制的已提交讀事務。 對於這種事務類型,只有事務中的每條語句需要數據庫的事務一致視圖。 這表示語句完成後將不再需要在事務中爲它創建的行版本。 但是,由事務中的每條語句創建的行版本將受到維護,直到事務完成。

當 tempdb 運行空間不足時,SQL Server 數據庫引擎強制收縮版本存儲區。 在執行收縮進程的過程中,尚未生成行版本且運行時間最長的事務被標記爲犧牲品。 在錯誤日誌中爲每個作爲犧牲品的事務生成消息 3967。 如果某個事務被標記爲犧牲品,則該事務不能再讀取版本存儲區中的行版本。 當其嘗試讀取行版本時,會生成消息 3966 且該事務會被回滾。 如果收縮進程成功,則 tempdb 中就有可用空間。 否則,tempdb 運行空間不足,並出現下列情況:

  • 寫操作繼續執行但不生成版本。 錯誤日誌中會生成一條信息消息 (3959),但寫數據的事務不受影響。

  • 嘗試訪問由於 tempdb 完全回滾而未生成的行版本的事務終止,並生成錯誤消息 3958。

數據行中使用的空間

每個數據庫行的結尾處最多可以使用 14 個字節記錄行版本控制信息。 行版本控制信息包含提交版本的事務的事務序列號和指向版本行的指針。 如果符合下列任何一種條件,則第一次修改行時或插入新行時添加這 14 個字節:

  • READ_COMMITTED_SNAPSHOT 或 ALLOW_SNAPSHOT_ISOLATION 選項爲 ON。
  • 表有觸發器。
  • 正在使用多個活動的結果集 (MARS)。
  • 當前正在對錶執行聯機索引生成操作。

如果符合下列所有條件,則第一次修改數據庫行時,將從行中刪除這 14 個字節:

  • READ_COMMITTED_SNAPSHOT 和 ALLOW_SNAPSHOT_ISOLATION 選項爲 OFF。
  • 表不再有觸發器。
  • 當前沒有使用 MARS。
  • 當前沒有執行聯機索引生成操作。

如果使用了行版本控制功能,則可能需要爲數據庫分配額外的磁盤空間,才能使每個數據庫行可多使用 14 個字節。 如果當前頁上沒有足夠的可用空間,則添加行版本控制信息會導致拆分索引頁或分配新的數據頁。 例如,如果平均行長度爲 100 個字節,則額外的 14 個字節會導致現有表增大 14%。

減少填充因子可能有助於避免或減少索引頁碎片。 若要查看錶或視圖的數據和索引的碎片信息,可以使用 sys.dm_db_index_physical_stats

大型對象中使用的空間

SQL Server 數據庫引擎支持以下六種數據類型(最多可以容納大小爲 2 GB 的大型字符串):nvarchar(max)varchar(max)varbinary(max)ntexttext、 和 image。 使用這些數據類型的大型字符串存儲在一系列與數據行鏈接的數據片段中。 行版本控制信息存儲在用於存儲這些大型字符串的每個片段中。 數據片段是表中專用於大型對象的頁集合。

新的大型值添加到數據庫中時,系統會爲它們分配數據片段,每個片段最多可以存儲 8040 個字節的數據。 早期版本的 SQL Server 數據庫引擎中,每個片段最多可以存儲 8080 個字節的 ntexttext 或 image 數據。

數據庫從早期版本的 SQL Server 升級到 SQL Server 時,現有的 ntexttext 和 image 大型對象 (LOB) 數據並未更新來爲行版本控制信息釋放一些空間。 但第一次修改 LOB 數據時,該數據會動態升級以實現版本控制信息的存儲。 即使未生成行版本也是如此。 LOB 數據升級後,每個片段最多可以存儲的字節數從 8080 個減少到 8040 個。 升級過程相當於先刪除 LOB 值再重新插入相同值。 即使只修改 1 個字節也會升級 LOB 數據。 對於每個 ntexttext 或 image 列,這是一次性操作,但每個操作可能生成大量頁分配和 I/O 活動,具體情況取決於 LOB 數據的大小。 如果完整記錄修改,還會生成大量日誌記錄活動。 如果數據庫恢復模式未設置爲 FULL,則按最小方式記錄 WRITETEXT 操作和 UPDATETEXT 操作。

在早期版本的 SQL Server 中不使用 nvarchar(max)varchar(max) 和 varbinary(max) 數據類型。 因此,這些數據類型不存在升級問題。

應該分配足夠的磁盤空間來滿足此要求。

監視行版本控制和版本存儲區

爲了監視行版本控制、版本存儲區和快照隔離進程以瞭解性能和問題,SQL Server 提供了一些工具,包括動態管理視圖 (DMV) 和 Windows 系統監視器中的性能計數器。

DMV

下列 DMV 提供有關 tempdb 的當前系統狀態、版本存儲區以及使用行版本控制的事務的信息。

  • sys.dm_db_file_space_usage。 返回數據庫中每個文件的空間使用信息。 有關詳細信息,請參閱 sys.dm_db_file_space_usage (Transact-SQL)

  • sys.dm_db_session_space_usage。 返回會話爲數據庫進行的頁分配和釋放活動。 有關詳細信息,請參閱 sys.dm_db_session_space_usage (Transact-SQL)

  • sys.dm_db_task_space_usage。 返回任務爲數據庫進行的頁分配和釋放活動。 有關詳細信息,請參閱 sys.dm_db_task_space_usage (Transact-SQL)

  • sys.dm_tran_top_version_generators。 返回一個虛擬表,其中包含生成的版本是版本存儲區中最多的對象。 該表按 database_id 和 rowset_id 對前 256 位的聚合記錄長度進行分組。 可以使用此函數來查找版本存儲區的最大使用者。 有關詳細信息,請參閱 sys.dm_tran_top_version_generators (Transact-SQL)

  • sys.dm_tran_version_store。 返回一個虛擬表,其中顯示有公共版本存儲區中的所有版本記錄。 有關詳細信息,請參閱 sys.dm_tran_version_store (Transact-SQL)

  • sys.dm_tran_version_store_space_usage。 返回一個虛擬表,該表顯示每個數據庫的版本存儲記錄使用的 tempdb 中的總空間。 有關詳細信息,請參閱 sys.dm_tran_version_store_space_usage (Transact-SQL)

     備註

    系統對象 sys.dm_tran_top_version_generators 和 sys.dm_tran_version_store 可能是運行起來非常昂貴的函數,因爲兩者都查詢整個版本存儲區,而版本存儲區可能非常大。 sys.dm_tran_version_store_space_usage 高效且運行開銷低,因爲它不會瀏覽單個版本存儲記錄,並返回每個數據庫 tempdb 中使用的聚合版本存儲空間。

  • sys.dm_tran_active_snapshot_database_transactions。 返回一個虛擬表,其中包含使用行版本控制的 SQL Server 實例中的所有數據庫中的所有活動事務。 但系統事務不會顯示在此 DMV 中。 有關詳細信息,請參閱 sys.dm_tran_active_snapshot_database_transactions (Transact-SQL)

  • sys.dm_tran_transactions_snapshot。 返回一個虛擬表,其中顯示有每個事務使用的快照。 該快照包含了使用行版本控制的活動事務的序列號。 有關詳細信息,請參閱 sys.dm_tran_transactions_snapshot (Transact-SQL)

  • sys.dm_tran_current_transaction。 返回一行,其中顯示有當前會話中與行版本控制相關的事務狀態信息。 有關詳細信息,請參閱 sys.dm_tran_current_transaction (Transact-SQL)

  • sys.dm_tran_current_snapshot。 返回一個虛擬表,其中顯示有當前快照隔離事務啓動時的所有活動事務。 如果當前事務正在使用快照隔離,則該函數不返回行。 DMV sys.dm_tran_current_snapshot 類似於 sys.dm_tran_transactions_snapshot,只不過它僅返回當前快照的活動事務。 有關詳細信息,請參閱 sys.dm_tran_current_snapshot (Transact-SQL)

性能計數器

SQL Server 性能計數器提供有關受 SQL Server 進程影響的系統性能的信息。 以下性能計數器監視 tempdb、版本存儲區以及使用行版本控制的事務。 這些性能計數器包含在 SQLServer:Transactions 性能對象中。

  • Free Space in tempdb (KB)。 監視 tempdb 數據庫中的可用空間 (KB)。 tempdb 中必須有足夠的可用空間來容納支持快照隔離的版本存儲區。

    下列公式可以用來粗略估計版本存儲區的大小。 對於長時間運行的事務,監視生成速率和清除速率對於估計版本存儲區的最大大小會非常有用。

    [公共版本存儲區的大小] = 2 * [每分鐘生成的版本存儲區數據] * [事務的最長運行時間(分鐘)]

    事務的最長運行時間不應該包括聯機索引生成時間。 對於超大型表,由於這些操作可能要花很長的時間,因此聯機索引生成使用單獨的版本存儲區。 當聯機索引生成處於活動狀態時,聯機索引生成版本存儲區的近似大小等於表(包括所有索引)中修改的數據量。

  • Version Store Size (KB)。 監視所有版本存儲區的大小 (KB)。 此信息有助於確定版本存儲區在 tempdb 數據庫中所需的空間大小。 監視計數器一段時間,可以獲得有用的信息來估計在 tempdb 數據庫中所需的額外空間。

  • Version Generation rate (KB/s)。 監視所有版本存儲區中的版本生成速率(KB/秒)。

  • Version Cleanup rate (KB/s)。 監視所有版本存儲區中的版本清除速率(KB/秒)。

     備註

    版本生成速率(KB/秒)和版本清理速率(KB/秒)的信息可以用於預測 tempdb 空間要求。

  • Version Store unit count。 監視版本存儲區單元的計數。

  • Version Store unit creation。 監視自啓動實例後創建用於存儲行版本的版本存儲區單元總數。

  • Version Store unit truncation。 監視自啓動實例後被截斷的版本存儲區單元總數。 當 SQL Server 確定不需要任何存儲在版本存儲區單元中的版本行來運行活動事務時,版本存儲區單元即被截斷。

  • Update conflict ratio。 監視存在更新衝突的更新快照事務與更新快照事務總數的比值。

  • Longest Transaction Running Time。 監視使用行版本控制的事務的最長運行時間(秒)。 這可用於確定是否存在事務的運行時間不合適的情況。

  • Transactions。 監視活動事務的總數, 不包括系統事務。

  • Snapshot Transactions。 監視活動快照事務的總數。

  • Update Snapshot Transactions。 監視執行更新操作的活動快照事務的總數。

  • NonSnapshot Version Transactions。 監視生成版本記錄的活動非快照事務的總數。

     備註

    Update Snapshot Transactions 與 NonSnapshot Version Transactions 之和表示參與版本生成的事務的總數。 Snapshot Transactions 與 Update Snapshot Transactions 之差表示只讀快照事務數。

基於行版本控制的隔離級別示例

下列示例說明使用行版本控制的快照隔離事務與已提交讀事務的行爲差異。

A. 使用快照隔離

在此示例中,在快照隔離下運行的事務將讀取數據,然後由另一事務修改此數據。 快照事務不阻塞由其他事務執行的更新操作,它忽略數據的修改繼續從版本化的行讀取數據。 但是,當快照事務嘗試修改已由其他事務修改的數據時,快照事務將生成錯誤並終止。

在會話 1 上:

SQL
USE AdventureWorks2022;
GO

-- Enable snapshot isolation on the database.
ALTER DATABASE AdventureWorks2022
    SET ALLOW_SNAPSHOT_ISOLATION ON;
GO

-- Start a snapshot transaction
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
GO

BEGIN TRANSACTION;
    -- This SELECT statement will return
    -- 48 vacation hours for the employee.
    SELECT BusinessEntityID, VacationHours
        FROM HumanResources.Employee
        WHERE BusinessEntityID = 4;

在會話 2 上:

SQL
USE AdventureWorks2022;
GO

-- Start a transaction.
BEGIN TRANSACTION;
    -- Subtract a vacation day from employee 4.
    -- Update is not blocked by session 1 since
    -- under snapshot isolation shared locks are
    -- not requested.
    UPDATE HumanResources.Employee
        SET VacationHours = VacationHours - 8
        WHERE BusinessEntityID = 4;

    -- Verify that the employee now has 40 vacation hours.
    SELECT VacationHours
        FROM HumanResources.Employee
        WHERE BusinessEntityID = 4;

在會話 1 上:

SQL
    -- Reissue the SELECT statement - this shows
    -- the employee having 48 vacation hours. The
    -- snapshot transaction is still reading data from
    -- the versioned row.
    SELECT BusinessEntityID, VacationHours
        FROM HumanResources.Employee
        WHERE BusinessEntityID = 4;

在會話 2 上:

SQL
-- Commit the transaction; this commits the data
-- modification.
COMMIT TRANSACTION;
GO

在會話 1 上:

SQL
    -- Reissue the SELECT statement - this still
    -- shows the employee having 48 vacation hours
    -- even after the other transaction has committed
    -- the data modification.
    SELECT BusinessEntityID, VacationHours
        FROM HumanResources.Employee
        WHERE BusinessEntityID = 4;

    -- Because the data has been modified outside of the
    -- snapshot transaction, any further data changes to
    -- that data by the snapshot transaction will cause
    -- the snapshot transaction to fail. This statement
    -- will generate a 3960 error and the transaction will
    -- terminate.
    UPDATE HumanResources.Employee
        SET SickLeaveHours = SickLeaveHours - 8
        WHERE BusinessEntityID = 4;

-- Undo the changes to the database from session 1.
-- This will not undo the change from session 2.
ROLLBACK TRANSACTION
GO

B. 使用通過行版本控制的已提交讀

在此示例中,使用行版本控制的已提交讀事務與其他事務併發運行。 已提交讀事務的行爲與快照事務的行爲有所不同。 與快照事務相同的是,即使其他事務修改了數據,已提交讀事務也將讀取版本化的行。 然而,與快照事務不同的是,已提交讀將執行下列操作:

  • 在其他事務提交數據更改後,讀取修改的數據。
  • 能夠更新由其他事務修改的數據,而快照事務不能。

在會話 1 上:

SQL
USE AdventureWorks2022;  -- Or any earlier version of the AdventureWorks database.
GO

-- Enable READ_COMMITTED_SNAPSHOT on the database.
-- For this statement to succeed, this session
-- must be the only connection to the AdventureWorks2022
-- database.
ALTER DATABASE AdventureWorks2022
    SET READ_COMMITTED_SNAPSHOT ON;
GO

-- Start a read-committed transaction
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
GO

BEGIN TRANSACTION;
    -- This SELECT statement will return
    -- 48 vacation hours for the employee.
    SELECT BusinessEntityID, VacationHours
        FROM HumanResources.Employee
        WHERE BusinessEntityID = 4;

在會話 2 上:

SQL
USE AdventureWorks2022;
GO

-- Start a transaction.
BEGIN TRANSACTION;
    -- Subtract a vacation day from employee 4.
    -- Update is not blocked by session 1 since
    -- under read-committed using row versioning shared locks are
    -- not requested.
    UPDATE HumanResources.Employee
        SET VacationHours = VacationHours - 8
        WHERE BusinessEntityID = 4;

    -- Verify that the employee now has 40 vacation hours.
    SELECT VacationHours
        FROM HumanResources.Employee
        WHERE BusinessEntityID = 4;

在會話 1 上:

SQL
    -- Reissue the SELECT statement - this still shows
    -- the employee having 48 vacation hours. The
    -- read-committed transaction is still reading data
    -- from the versioned row and the other transaction
    -- has not committed the data changes yet.
    SELECT BusinessEntityID, VacationHours
        FROM HumanResources.Employee
        WHERE BusinessEntityID = 4;

在會話 2 上:

SQL
-- Commit the transaction.
COMMIT TRANSACTION;
GO

在會話 1 上:

SQL
    -- Reissue the SELECT statement which now shows the
    -- employee having 40 vacation hours. Being
    -- read-committed, this transaction is reading the
    -- committed data. This is different from snapshot
    -- isolation which reads from the versioned row.
    SELECT BusinessEntityID, VacationHours
        FROM HumanResources.Employee
        WHERE BusinessEntityID = 4;

    -- This statement, which caused the snapshot transaction
    -- to fail, will succeed with read-committed using row versioning.
    UPDATE HumanResources.Employee
        SET SickLeaveHours = SickLeaveHours - 8
        WHERE BusinessEntityID = 4;

-- Undo the changes to the database from session 1.
-- This will not undo the change from session 2.
ROLLBACK TRANSACTION;
GO

啓用基於行版本控制的隔離級別

數據庫管理員可以通過在 ALTER DATABASE 語句中使用 READ_COMMITTED_SNAPSHOT 和 ALLOW_SNAPSHOT_ISOLATION 數據庫選項來控制行版本控制的數據庫級別設置。

當 READ_COMMITTED_SNAPSHOT 數據庫選項設置爲 ON 時,用於支持該選項的機制將立即激活。 設置 READ_COMMITTED_SNAPSHOT 選項時,數據庫中只允許存在執行 ALTER DATABASE 命令的連接。 在 ALTER DATABASE 完成之前,數據庫中不允許有其他打開的連接。 數據庫不必一定要處於單用戶模式下。

以下 Transact-SQL 語句啓用 READ_COMMITTED_SNAPSHOT

SQL
ALTER DATABASE AdventureWorks2022
    SET READ_COMMITTED_SNAPSHOT ON;

ALLOW_SNAPSHOT_ISOLATION 數據庫選項設置爲 ON 時,數據庫中數據已修改的所有活動事務完成之前,SQL Server 數據庫引擎實例不會爲已修改的數據生成行版本。 如果存在活動的修改事務,SQL Server 將該選項的狀態設置爲 PENDING_ON。 所有修改事務完成後,該選項的狀態更改爲 ON。 在該選項完全處於 ON 狀態之前,用戶無法在數據庫中啓動快照事務。 數據庫管理員將 ALLOW_SNAPSHOT_ISOLATION 選項設置爲 OFF 時,數據庫將跳過 PENDING_OFF 狀態。

以下 Transact-SQL 語句將啓用 ALLOW_SNAPSHOT_ISOLATION:

SQL
ALTER DATABASE AdventureWorks2022
    SET ALLOW_SNAPSHOT_ISOLATION ON;

下表列出並說明了 ALLOW_SNAPSHOT_ISOLATION 選項的各個狀態。 同時使用 ALTER DATABASE 和 ALLOW_SNAPSHOT_ISOLATION 選項不會妨礙當前正在訪問數據庫數據的用戶。

當前數據庫的快照隔離框架狀態說明
OFF 未啓用對快照隔離事務的支持。 不允許執行快照隔離事務。
PENDING_ON 對快照隔離事務的支持處於轉換狀態(從 OFF 到 ON)。 打開的事務必須完成。

不允許執行快照隔離事務。
ON 已啓用對快照隔離事務的支持。

允許執行快照事務。
PENDING_OFF 對快照隔離事務的支持處於轉換狀態(從 ON 到 OFF)。

此後啓動的快照事務無法訪問此數據庫。 更新事務仍會導致此數據庫中出現版本控制開銷。 現有快照事務仍可以訪問此數據庫,不會遇到任何問題。 直到數據庫快照隔離狀態爲 ON 時處於活動狀態的所有快照事務完成後,狀態 PENDING_OFF 才變爲 OFF。

使用 sys.databases 目錄視圖可以確定兩個行版本控制數據庫選項的狀態。

對用戶表和存儲在 master 和 msdb 中的某些系統表的任何更新都會生成行版本。

在 ALLOW_SNAPSHOT_ISOLATION 和 master 數據庫中,msdb 選項自動設置爲 ON,並且不能禁用。

在 READ_COMMITTED_SNAPSHOTmaster 或 tempdb 中,用戶不能將 msdb 選項設置爲 ON。

使用基於行版本控制的隔離級別

行版本控制框架在 SQL Server 中始終處於啓用狀態,並被多個功能使用。 它除了提供基於行版本控制的隔離級別之外,還用於支持對觸發器和多個活動結果集 (MARS) 會話的修改,以及 ONLINE 索引操作的數據讀取。

基於行版本控制的隔離級別是在數據庫級別上啓用的。 訪問已啓用數據庫的對象的任何應用程序可以使用以下隔離級別運行查詢:

  • 已提交讀隔離級別,通過將 READ_COMMITTED_SNAPSHOT 數據庫選項設置爲 ON 來使用行版本控制,如下面的代碼示例所示:

    SQL
    ALTER DATABASE AdventureWorks2022
        SET READ_COMMITTED_SNAPSHOT ON;
    

    爲 READ_COMMITTED_SNAPSHOT 啓用數據庫後,在 READ COMMITTED 隔離級別下運行的所有查詢將使用行版本控制,這意味着讀取操作不會阻止更新操作。

  • 快照隔離,通過將 ALLOW_SNAPSHOT_ISOLATION 數據庫選項設置爲 ON 實現,如下面的代碼示例所示:

    SQL
    ALTER DATABASE AdventureWorks2022
        SET ALLOW_SNAPSHOT_ISOLATION ON;
    

    在快照隔離下運行的事務可以訪問數據庫中爲快照啓用的表。 若要訪問沒有爲快照啓用的表,則必須更改隔離級別。 例如,下面的代碼示例顯示了在快照事務下運行時聯接兩個表的 SELECT 語句。 一個表屬於未啓用快照隔離的數據庫。 當 SELECT 語句在快照隔離下運行時,該語句無法成功執行。

    SQL
    SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
    BEGIN TRAN
        SELECT t1.col5, t2.col5
            FROM Table1 as t1
            INNER JOIN SecondDB.dbo.Table2 as t2
                ON t1.col1 = t2.col2;
    

    下面的代碼示例顯示了已修改爲從事務隔離級別更改爲已提交讀隔離級別的相同 SELECT 語句。 由於此更改,SELECT 語句將成功執行。

    SQL
    SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
    BEGIN TRAN
        SELECT t1.col5, t2.col5
            FROM Table1 as t1
            WITH (READCOMMITTED)
            INNER JOIN SecondDB.dbo.Table2 as t2
                ON t1.col1 = t2.col2;
    

使用基於行版本控制的隔離級別的事務的限制

使用基於行版本控制的隔離級別時,請考慮下列限制:

  • READ_COMMITTED_SNAPSHOT 不能在 tempdbmsdb 或 master 中啓用。

  • 全局臨時表存儲在 tempdb 中。 訪問快照事務中的全局臨時表時,必須執行下列操作之一:

    • 在 ALLOW_SNAPSHOT_ISOLATION 中將 tempdb 數據庫選項設置爲 ON。
    • 使用隔離提示更改語句的隔離級別。
  • 如果出現以下情況,快照事務將失敗:

    • 從快照事務啓動之後到訪問數據庫前的期間內,數據庫設置爲只讀。
    • 如果訪問多個數據庫的對象,數據庫狀態以如下方式更改:從快照事務啓動後到訪問數據庫前的期間內,發生數據庫恢復。 例如:將數據庫設置爲 OFFLINE,然後設置爲 ONLINE,數據庫將自動關閉和打開,或數據庫將分離和附加。
  • 快照隔離不支持分佈式事務,包括分佈式分區數據庫中的查詢。

  • SQL Server 不會保留系統元數據的多個版本。 表中的數據定義語言 (DDL) 語句和其他數據庫對象(索引、視圖、數據類型、存儲過程和公共語言運行時函數)會更改元數據。 如果 DDL 語句修改一個對象,那麼在快照隔離下對該對象的任何併發引用都將導致快照事務失敗。 READ_COMMITTED_SNAPSHOT 數據庫選項爲 ON 時,已提交讀事務沒有此限制。

    例如,數據庫管理員執行下面的 ALTER INDEX 語句。

    SQL
    USE AdventureWorks2022;
    GO
    ALTER INDEX AK_Employee_LoginID
        ON HumanResources.Employee REBUILD;
    GO
    

    執行 ALTER INDEX 語句後,任何在執行 HumanResources.Employee 語句時處於活動狀態的快照事務,如果試圖引用 ALTER INDEX 表,都將收到錯誤。 而使用行版本控制的已提交讀事務不受影響。

     備註

    BULK INSERT 操作可能會導致對目標表元數據的更改(例如,禁用約束檢查時)。 如果出現這種情況,訪問大容量插入表的併發快照隔離事務將失敗。

自定義鎖定和行版本控制

自定義鎖超時

當 Microsoft SQL Server 數據庫引擎實例由於其他事務已擁有資源的衝突鎖而無法將鎖授予給某個事務時,將阻止第一個事務,等待現有鎖釋放。 默認情況下,沒有強制的超時期限,並且除了嘗試訪問數據(有可能被無限期阻塞)外,沒有其他方法可以測試某個資源是否在鎖定之前已被鎖定。

 備註

在 SQL Server 中,使用 sys.dm_os_waiting_tasks 動態管理視圖確定某個進程是否被阻塞以及被誰阻塞。 在 SQL Server 的早期版本中,使用 sp_who 系統存儲過程。 有關詳細信息和示例,請參閱瞭解並解決 SQL Server 阻塞問題

LOCK_TIMEOUT 設置允許應用程序設置語句等待阻塞資源的最長時間。 如果某個語句等待的時間超過 LOCK_TIMEOUT 的設置時間,則被阻塞的語句自動取消,並會有錯誤消息 1222 (Lock request time-out period exceeded) 返回給應用程序。 但是,SQL Server 不會回滾或取消任何包含語句的事務。 因此,應用程序必須具有可以捕獲錯誤消息 1222 的錯誤處理程序。 如果應用程序不能捕獲錯誤,則會在不知道事務中已有個別語句被取消的情況下繼續運行,由於事務中後面的語句可能依賴於從未執行過的語句,因此會出現錯誤。

實現捕獲錯誤消息 1222 的錯誤處理程序後,應用程序可以處理超時情況,並採取補救措施,例如:自動重新提交被阻塞的語句或回滾整個事務。

若要確定當前的 LOCK_TIMEOUT 設置,請執行 @@LOCK_TIMEOUT 函數:

SQL
SELECT @@lock_timeout;
GO

自定義事務隔離級別

Microsoft SQL Server 數據庫引擎的默認隔離級別爲 READ COMMITTED。 如果應用程序必須在其他隔離級別運行,則它可以使用以下方法設置隔離級別:

  • 運行 SET TRANSACTION ISOLATION LEVEL 語句。
  • 使用 System.Data.SqlClient 託管命名空間的 ADO.NET 應用程序可以使用 SqlConnection.BeginTransaction 方法指定 IsolationLevel 選項。
  • 使用了 ADO 的應用程序可以設置 Autocommit Isolation Levels 屬性。
  • 啓動事務時,使用 OLE DB 的應用程序可以調用 ITransactionLocal::StartTransaction,並將 isoLevel 設置爲所需的事務隔離級別。 在自動提交模式下指定隔離級別時,使用 OLE DB 的應用程序可以將 DBPROPSET_SESSION 屬性 DBPROP_SESS_AUTOCOMMITISOLEVELS 設置爲所需的事務隔離級別。
  • 使用 ODBC 的應用程序可以使用 SQLSetConnectAttr 來設置 SQL_COPT_SS_TXN_ISOLATION

指定隔離級別後,SQL Server 會話中的所有查詢語句和數據操作語言 (DML) 語句的鎖定行爲都將在該隔離級別進行操作。 隔離級別將在會話終止或將其設置爲其他級別後失效。

下面的示例設置 SERIALIZABLE 隔離級別:

SQL
USE AdventureWorks2022;
GO
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
GO
BEGIN TRANSACTION;
SELECT BusinessEntityID
    FROM HumanResources.Employee;
GO

必要時,可以通過指定表級提示來替代各個查詢語句或 DML 語句的隔離級別。 指定表級提示不會影響會話中的其他語句。 建議僅在確實必要時才使用表級提示更改默認行爲。

讀取元數據時,甚至當隔離級別設置爲在讀取數據時不請求共享鎖的級別時,SQL Server 數據庫引擎也可能需要獲取鎖。 例如,在未提交讀隔離級別下運行的事務在讀取數據時將不獲取共享鎖,但是在讀取系統目錄視圖時可能會請求鎖。 這意味着在查詢表時如果某個併發事務正在修改該表的元數據,則未提交讀事務可能會導致阻塞。

若要確定當前設置的事務隔離級別,請使用 DBCC USEROPTIONS 語句,如下面的示例所示。 該結果集可能與系統的結果集不同。

SQL
USE AdventureWorks2022;
GO
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
GO
DBCC USEROPTIONS;
GO

下面是結果集:

Set Option                   Value
---------------------------- -------------------------------------------
textsize                     2147483647
language                     us_english
dateformat                   mdy
datefirst                    7
...                          ...
Isolation level              repeatable read

(14 row(s) affected)

DBCC execution completed. If DBCC printed error messages, contact your system administrator.

鎖提示

可以在 SELECT、INSERT、UPDATE 及 DELETE 語句中爲單個表引用指定鎖提示。 提示指定 SQL Server 數據庫引擎實例用於表數據的鎖類型或行版本控制。 當需要對對象所獲得鎖類型進行更精細控制時,可以使用表級鎖提示。 這些鎖提示覆蓋會話的當前事務隔離級別。

 備註

啓用優化鎖定時,不建議使用鎖定提示。 儘管遵循表和查詢提示,但它們會減少優化鎖定的優勢。 有關詳細信息,請參閱 避免對優化鎖定使用鎖定提示

有關特定鎖定提示及其行爲的詳細信息,請參閱表提示 (Transact-SQL)

 備註

SQL Server 數據庫引擎幾乎總是會選擇正確的鎖定級別。 建議只在必要時才使用表級鎖提示來更改默認的鎖行爲。 禁止鎖級別反過來會影響併發。

SQL Server 數據庫引擎在讀取元數據時可能必須獲取鎖,即使是處理使用了防止在讀取數據時請求共享鎖的鎖提示的選擇。 例如,使用 NOLOCK 提示的 SELECT 在讀取數據時不獲取共享鎖,但有時在讀取系統目錄視圖時可能會請求鎖。 這意味着可能會阻止使用 NOLOCK 的 SELECT語句。

如以下示例中所示,如果將事務隔離級別設置爲 SERIALIZABLE,並且在 NOLOCK 語句中使用表級鎖提示 SELECT,則不獲取通常用於維護可序列化事務的鍵範圍鎖。

SQL
USE AdventureWorks2022;
GO
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
GO
BEGIN TRANSACTION;
GO
SELECT JobTitle
    FROM HumanResources.Employee WITH (NOLOCK);
GO

-- Get information about the locks held by
-- the transaction.
SELECT
        resource_type,
        resource_subtype,
        request_mode
    FROM sys.dm_tran_locks
    WHERE request_session_id = @@spid;

-- End the transaction.
ROLLBACK;
GO

引用 HumanResources.Employee 唯一獲取的鎖是架構穩定性 (Sch-S) 鎖。 在這種情況下,不再保證可序列化性。

在 SQL Server 中,LOCK_ESCALATION 的 ALTER TABLE 選項可以禁用表鎖,並在已分區表上啓用 HoBT 鎖。 此選項不是一個鎖提示,但是可用來減少鎖升級。 有關詳細信息,請參閱 ALTER TABLE (Transact-SQL)

自定義索引的鎖定

SQL Server 數據庫引擎使用動態鎖定策略,這種策略能夠在大多數情況下自動爲查詢選擇最佳鎖定粒度。 建議你不要替代啓用頁鎖定和行鎖定的默認鎖定級別,除非透徹地瞭解了表或索引的訪問模式且這些訪問模式保持一致,並且存在有待解決的資源爭用問題。 替代鎖定級別可以明顯妨礙對錶或索引的併發訪問。 例如,對用戶時常訪問的大型表僅指定表級鎖可能會造成瓶頸,因爲用戶必須等待表級鎖釋放後才能訪問該表。

在爲數不多的情況下,不允許頁鎖定或行鎖定可能會有好處,但必須透徹地瞭解訪問模式且這些訪問模式保持一致。 例如,某個數據庫應用程序使用的查找表在批處理進程中每週更新一次。 併發讀取器使用共享鎖 (S) 訪問表,每週批處理更新使用排他鎖 (X) 訪問表。 關閉表的頁鎖定和行鎖定可以使讀取器通過共享表鎖對錶進行併發訪問,從而在整週內降低鎖定開銷。 在批處理作業運行時,由於它獲得了排他表鎖,因此可以高效地完成更新。

由於每週批處理更新在運行時將阻止併發讀取器訪問表,因此關閉頁鎖定和行鎖定可能是可取的,也可能不可取。 如果批處理作業僅更改少數幾行或幾頁,則可以更改鎖定級別以允許行級別或頁級別的鎖定,這將允許其他會話讀取表中的數據而不會受到阻止。 如果批處理作業要進行大量更新,則獲取表的排他鎖可能是確保批處理作業高效完成的最佳途徑。

當兩個併發操作獲得同一個表的行鎖然後進行阻止時,偶爾會出現死鎖,因爲這兩個操作都需要鎖定該頁。 如果不允許使用行鎖,則會強行使其中一個操作等待,從而避免死鎖。 有關死鎖的詳細信息,請參閱死鎖指南

使用 CREATE INDEX 和 ALTER INDEX 語句來設置索引使用的鎖定粒度。 該鎖設置適用於索引頁和表頁。 此外,CREATE TABLE 和 ALTER TABLE 語句可用於設置 PRIMARY KEY 和 UNIQUE 約束上的鎖定粒度。 對於後向兼容,還可以使用 sp_indexoption 系統存儲過程設置粒度。 若要顯示給定索引的當前鎖定選項,請使用 INDEXPROPERTY 函數。 可以禁止將頁級別的鎖、行級別的鎖或二者的組合用於指定的索引。

禁止的鎖訪問索引的鎖
頁面級別 行級別的鎖和表級別的鎖
行級別 頁級別的鎖和表級別的鎖
頁級別和行級別 表級別的鎖

高級事務信息

嵌套事務

顯式事務可以嵌套。 這主要是爲了支持存儲過程中的一些事務,這些事務可以從已在事務中的進程調用,也可以從沒有活動事務的進程中調用。

下列示例顯示了嵌套事務的用途。 TransProc 過程強制執行其事務,而不管執行事務的進程的事務模式。 如果在事務活動時調用 TransProc,很可能會忽略 TransProc 中的嵌套事務,而根據對外部事務獲取的最終操作提交或回滾其 INSERT 語句。 如果由不含未完成事務的進程執行 TransProc,該過程結束時,COMMIT TRANSACTION 將有效地提交 INSERT 語句。

SQL
SET QUOTED_IDENTIFIER OFF;
GO
SET NOCOUNT OFF;
GO
CREATE TABLE TestTrans(Cola INT PRIMARY KEY,
               Colb CHAR(3) NOT NULL);
GO
CREATE PROCEDURE TransProc @PriKey INT, @CharCol CHAR(3) AS
BEGIN TRANSACTION InProc
INSERT INTO TestTrans VALUES (@PriKey, @CharCol)
INSERT INTO TestTrans VALUES (@PriKey + 1, @CharCol)
COMMIT TRANSACTION InProc;
GO
/* Start a transaction and execute TransProc. */
BEGIN TRANSACTION OutOfProc;
GO
EXEC TransProc 1, 'aaa';
GO
/* Roll back the outer transaction, this will
   roll back TransProc's nested transaction. */
ROLLBACK TRANSACTION OutOfProc;
GO
EXECUTE TransProc 3,'bbb';
GO
/* The following SELECT statement shows only rows 3 and 4 are
   still in the table. This indicates that the commit
   of the inner transaction from the first EXECUTE statement of
   TransProc was overridden by the subsequent rollback. */
SELECT * FROM TestTrans;
GO

SQL Server 數據庫引擎將忽略內部事務的提交。 根據最外部事務結束時獲取的操作,將提交或者回滾內部事務。 如果提交外部事務,也將提交內部嵌套事務。 如果回滾外部事務,也將回滾所有內部事務,不管是否單獨提交過內部事務。

對 COMMIT TRANSACTION 或 COMMIT WORK 的每次調用都適用於上次執行的 BEGIN TRANSACTION。 如果嵌套 BEGIN TRANSACTION 語句,那麼 COMMIT 語句只應用於最後一個嵌套的事務,也就是在最內部的事務。 即使嵌套事務內部的 COMMIT TRANSACTION transaction_name 語句引用外部事務的事務名稱,該提交也只應用於最內部的事務。

ROLLBACK TRANSACTION 語句的 transaction_name 參數引用一組已命名的嵌套事務的內部事務是不合法的。 transaction_name 只能引用最外部事務的事務名稱。 如果在一組嵌套事務的任意級別執行使用外部事務名稱的 ROLLBACK TRANSACTION transaction_name 語句,那麼所有嵌套事務都將回滾。 如果在一組嵌套事務的任意級別執行沒有 transaction_name 參數的 ROLLBACK WORK 或 ROLLBACK TRANSACTION 語句,那麼所有嵌套事務都將回滾,包括最外部事務。

@@TRANCOUNT 函數記錄當前事務的嵌套級別。 每個 BEGIN TRANSACTION 語句以 1 爲增量遞增 @@TRANCOUNT。 每個 COMMIT TRANSACTION 或 COMMIT WORK 語句以 1 爲增量遞增 @@TRANCOUNT。 沒有事務名稱的 ROLLBACK WORK 或 ROLLBACK TRANSACTION 語句將回滾所有嵌套事務,並將 @@TRANCOUNT 遞減到 0。 在一組嵌套事務中,使用最外部事務的事務名稱的 ROLLBACK TRANSACTION 將回滾所有嵌套事務,並將 @@TRANCOUNT 減小到 0。 在無法確定是否已經在事務中時,可使用 SELECT @@TRANCOUNT 確定是等於 1 還是大於 1。 如果 @@TRANCOUNT 爲 0,表明不在事務中。

使用綁定會話

綁定會話有利於在同一臺服務器上的多個會話之間協調操作。 綁定會話允許一個或多個會話共享相同的事務和鎖,並可以使用同一數據,而不會有鎖衝突。 可以從同一個應用程序內的多個會話中創建綁定會話,也可以從包含不同會話的多個應用程序中創建綁定會話。

要參與綁定會話,會話必須調用 sp_getbindtoken 或 srv_getbindtoken(通過開放式數據服務)來獲取綁定令牌。 綁定令牌是一個字符串,它唯一地標識每個綁定事務。 然後,將綁定令牌發送給要與當前會話綁定的其他會話。 其他會話通過調用 sp_bindsession,並使用從第一個會話中接收到的綁定令牌綁定到事務。

 備註

會話必須包含活動的用戶事務,sp_getbindtoken 或 srv_getbindtoken 才能成功。

必須將綁定令牌從執行第一個會話的應用程序代碼傳輸到隨後將其會話綁定到第一個會話的應用程序代碼。 沒有應用程序可以用來獲取由另一個進程啓動的事務綁定令牌的 Transact-SQL 語句或 API 函數。 可以用來傳輸綁定令牌的方法包括:

  • 如果所有會話都是從同一個應用程序進程啓動的,綁定令牌就可以存儲在共用內存中,也可以作爲參數傳遞到函數中。

  • 如果會話是從不同的應用程序進程啓動的,那麼可以使用進程間通信 (IPC)(例如,遠程過程調用 [RPC] 或動態數據交換 [DDE])來傳輸綁定令牌。

  • 可以將綁定令牌存儲在 SQL Server 數據庫引擎實例中的某個表中,該表可由要綁定到第一個會話的進程讀取。

在一組綁定會話中,任何時候只能有一個會話是活動的。 如果有一個會話正在實例上執行一個語句,或包含從實例掛起的結果,則在當前會話完成處理或取消當前語句之前,其他綁定到該會話的會話都不能訪問該實例。 如果該實例正在忙於處理來自另一個綁定會話的語句,則將出現錯誤,指明事務空間正在使用中,會話應稍後重試。

綁定會話後,每個會話仍保留其隔離級別設置。 使用 SET TRANSACTION ISOLATION LEVEL 更改某個會話的隔離級別設置不會影響綁定到該會話的任何其他會話的設置。

綁定會話的類型

有兩種類型的綁定會話:本地綁定會話和分佈式綁定會話。

  • 本地綁定會話允許綁定會話共享 SQL Server 數據庫引擎的單個實例中的單個事務的事務空間。

  • 分佈式事務允許在使用 Microsoft 分佈式事務處理協調器 (MS DTC) 提交或回滾整個事務之前,綁定會話可以共享跨越兩個或多個實例的同一事務。

分佈式綁定會話不是用字符串綁定令牌標識,而是用分佈式事務標識號標識。 如果本地事務中涉及到綁定會話,且該會話在遠程服務器上使用 SET REMOTE_PROC_TRANSACTIONS ON 執行 RPC,MS DTC 將該本地綁定事務自動提升爲分佈式綁定事務,並且 MS DTC 會話也會啓動。

何時使用綁定會話

在早期版本的 SQL Server 中,綁定會話主要用於開發必須執行 Transact-SQL 語句(代表調用它們的進程)的擴展存儲過程。 讓調用進程在綁定令牌中作爲擴展存儲過程的一個參數進行傳遞,可使該過程加入到調用進程的事務空間中,從而將擴展存儲過程與該調用進程結合在一起。

在 SQL Server 數據庫引擎中,使用 CLR 編寫的存儲過程比擴展存儲過程更安全、具有更高的伸縮性並且更穩定。 CLR 存儲過程使用 SqlContext 對象(而非 sp_bindsession)聯接調用會話的上下文。

綁定會話可以用來開發三層應用程序,在這些應用程序中,業務邏輯合併到在單個業務事務上協同工作的單獨程序中。 必須對這些程序進行編碼,以仔細協調它們對數據庫的訪問。 由於兩個會話共享同一個鎖,因此兩個程序不得同時修改同一數據。 在任何時間點,事務中只能有一個會話在執行,不存在並行執行操作。 只能在定義完善的時間點於會話之間切換事務,例如,已完成所有 DML 語句且已檢索其結果時。

編寫有效的事務

儘可能使事務保持簡短很重要。 當事務啓動後,數據庫管理系統 (DBMS) 必須在事務結束之前保留很多資源,以保護事務的原子性、一致性、隔離性和持久性 (ACID) 屬性。 如果修改數據,則必須用排他鎖保護修改過的行,以防止任何其他事務讀取這些行,並且必須將排他鎖控制到提交或回滾事務時爲止。 根據事務隔離級別設置,SELECT 語句可以獲取必須控制到提交或回滾事務時爲止的鎖。 特別是在有很多用戶的系統中,必須儘可能使事務保持簡短以減少併發連接間的資源鎖定爭奪。 在有少量用戶的系統中,運行時間長、效率低的事務可能不會成爲問題,但是在有上千個用戶的系統中,將不能忍受這樣的事務。 從 SQL Server 2014 (12.x) 開始,SQL Server 支持延遲持久事務。 延遲持久事務並不保證持續性。 有關詳細信息,請參閱控制事務持續性

代碼指南

以下是編寫有效事務的指導原則:

  • 不要在事務處理期間要求用戶輸入。 在事務啓動之前,獲得所有需要的用戶輸入。 如果在事務處理期間還需要其他用戶輸入,則回滾當前事務,並在提供了用戶輸入之後重新啓動該事務。 即使用戶立即響應,作爲人,其反應時間也要比計算機慢得多。 事務佔用的所有資源都要保留相當長的時間,這有可能會造成阻塞問題。 如果用戶沒有響應,事務仍然會保持活動狀態,從而鎖定關鍵資源直到用戶響應爲止,但是用戶可能會幾分鐘甚至幾個小時都不響應。

  • 在瀏覽數據時,儘量不要打開事務。 在所有預備的數據分析完成之前,不應啓動事務。

  • 儘可能使事務保持簡短。 在知道要進行的修改之後,啓動事務,執行修改語句,然後立即提交或回滾。 只有在需要時纔打開事務。

  • 若要減少阻塞,請考慮針對只讀查詢使用基於行版本控制的隔離級別。

  • 靈活地使用更低的事務隔離級別。 可以很容易地編寫出許多使用只讀事務隔離級別的應用程序。 並不是所有事務都要求可序列化的事務隔離級別。

  • 靈活地使用更低的遊標併發選項,例如開放式併發選項。 在併發更新的可能性很小的系統中,處理“別人在你讀取數據後更改了數據”的偶然錯誤的開銷要比在讀取數據時始終鎖定行的開銷小得多。

  • 在事務中儘量使訪問的數據量最小。 這樣可以減少鎖定的行數,從而減少事務之間的爭奪。

  • 儘可能避免使用悲觀鎖定提示(如 holdlock)。 諸如 HOLDLOCK 或 SERIALIZABLE 隔離級別之類的提示可能會導致進程即使在獲取共享鎖時也要等待,並且會降低併發性

  • 儘可能避免使用隱式事務。隱式事務會因其性質而導致不可預知的行爲。 請參閱隱式事務和併發問題

  • 使用縮減的填充因子設計索引。縮減填充因子可能有助於避免或減少索引頁碎片,從而減少索引搜尋時間,尤其是從磁盤檢索時。 要查看錶或視圖的數據和索引的碎片信息,可以使用 sys.dm_db_index_physical_stats

隱式事務以及避免併發問題和資源問題

爲了防止併發問題和資源問題,應小心管理隱式事務。 使用隱式事務時,COMMIT 或 ROLLBACK 後的下一個 Transact-SQL 語句會自動啓動一個新事務。 這可能會在應用程序瀏覽數據時(甚至在需要用戶輸入時)打開一個新事務。 在完成保護數據修改所需的最後一個事務之後,應關閉隱性事務,直到再次需要使用事務來保護數據修改。 此過程使 SQL Server 數據庫引擎能夠在應用程序瀏覽數據以及獲取用戶輸入時使用自動提交模式。

另外,啓用快照隔離級別後,儘管新事務不會控制鎖,但是長時間運行的事務將阻止從 tempdb中刪除舊版本。

管理長時間運行的事務

長時間運行的事務是一個未及時提交或回滾事務的活動事務。 例如,如果事務的開始和結束由用戶控制,則導致長時間運行事務的一般原因是用戶在開始事務之後便離開,而事務等待用戶的響應。

長時間運行的事務可能導致數據庫的嚴重問題,如下所示:

  • 如果服務器實例在活動事務已執行很多未提交的修改後關閉,後續重新啓動的恢復階段持續時間將遠遠多於恢復間隔服務器配置選項或 ALTER DATABASE ... SET TARGET_RECOVERY_TIME 選項指定的時間。 這些選項分別控制活動檢查點和間接檢查點的頻率。 有關檢查點類型的詳細信息,請參閱數據庫檢查點 (SQL Server)

  • 更重要的是,儘管等待事務可能生成很小的日誌,但是它無限期阻止日誌截斷,導致事務日誌不斷增加並可能填滿。 如果事務日誌填滿,數據庫將無法再執行任何更新。 有關詳細信息,請參閱 SQL Server 事務日誌體系結構和管理指南解決事務日誌已滿的問題(SQL Server 錯誤 9002)事務日誌

 重要

在 Azure SQL 數據庫中,空閒事務(6 小時內未寫入事務日誌的事務)會自動終止,以釋放資源。

發現長時間運行的事務

若要查看長時間運行的事務,請使用下列方法之一:

  • sys.dm_tran_database_transactions

    此動態管理視圖返回有關數據庫級事務的信息。 對於長時間運行的事務,需要關注的列包括:第一條日誌記錄的時間 (database_transaction_begin_time)、事務的當前狀態 (database_transaction_state) 和事務日誌中開始記錄的日誌序列號 (LSN) (database_transaction_begin_lsn)。

    有關詳細信息,請參閱 sys.dm_tran_database_transactions (Transact-SQL)

  • DBCC OPENTRAN

    通過此語句,你可以標識該事務所有者的用戶 ID,因此可以隱性地跟蹤該事務的源以得到更加有序的終止(將其提交而非回滾)。 有關詳細信息,請參閱 DBCC OPENTRAN (Transact-SQL)

停止事務

你可能必須使用 KILL 語句。 但是,在使用此語句時請務必小心,特別是在運行重要的進程時。 有關詳細信息,請參閱 KILL (Transact-SQL)

死鎖數

死鎖是與鎖定相關的複雜主題,但與阻止不同。

相關內容

 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章