优化锁定

适用于:SQL Server 2025(17.x) 预览版Azure SQL 数据库Azure SQL 托管实例Microsoft Fabric 预览版中的 SQL 数据库

优化锁定提供了改进的事务锁定机制,以减少并发事务的锁定阻塞和锁定内存消耗。

什么是优化锁定?

因为即使是大型事务持有的锁也很少,所以优化锁定有助于减少锁内存。 此外,优化锁定还可以避免锁升级。 这允许对表进行更多并发访问。

优化锁定由两个主要部分组成:事务 ID (TID) 锁定限定后锁定 (LAQ)

  • 事务 ID (TID) 是事务的唯一标识符。 每一行都标有修改它的最后一个 TID。 使用 TID 上的单个锁,而不是使用多个键或行标识符锁。 有关详细信息,请参阅事务 ID (TID) 锁定
  • 限定后锁定 (LAQ) 是一种优化,它在不获取锁定的情况下,使用行的最新提交版本评估查询谓词,从而提高并发性。 有关详细信息,请参阅限定后锁定 (LAQ)

例如:

  • 如果没有优化锁定,更新表中的 1,000 行可能需要持有 1,000 个排他 (X) 行锁,直到事务结束。
  • 借助优化锁定,更新表中的 1,000 行可能需要 1,000 个 X 行锁,但在更新每行后立即释放每个锁定,并且在事务结束之前,只持有一个 TID 锁。 由于很快就会释放锁,因此会减少锁内存使用量,并且发生锁升级的可能性要小得多,从而提高工作负荷并发性。

Note

启用优化锁定可以减少或消除数据修改语言 (DML) 语句(例如 INSERTUPDATEDELETEMERGE)获取的行锁和页锁。 这不会影响其他类型的数据库和对象锁,例如架构锁。

Availability

下表汇总了跨 SQL 平台优化锁定的可用性和启用状态。

Platform Available 默认启用
Azure SQL 数据库 Yes 是(始终启用)
Microsoft Fabric 预览版中的 SQL 数据库 Yes 是(始终启用)
Azure SQL 托管实例AUTD Yes 是(始终启用)
Azure SQL 托管实例2022 No N/A
SQL Server 2025 (17.x) 预览版 Yes 否(可为单个数据库启用)
SQL Server 2022 (16.x) 和旧版本 No N/A

启用和禁用

若要启用或禁用 SQL Server 数据库的优化锁定,请使用 ALTER DATABASE ... SET OPTIMIZED_LOCKING = ON | OFF 该命令。 有关详细信息,请参阅 ALTER DATABASE SET 选项

优化锁定建立在其他数据库功能的基础上:

  • 必须先在数据库上启用 加速数据库恢复(ADR), 然后才能启用优化的锁定。 相反,若要禁用 ADR,必须先禁用优化的锁定(如果已启用)。
  • 为充分利用优化锁定,应为数据库启用读提交快照隔离 (RCSI)。 仅当启用了 RCSI 时,优化锁定的 LAQ 组件才会生效。

在 Azure SQL 数据库和 Azure SQL 托管实例中,ADR 始终处于启用状态,并且默认启用 RCSI。

若要确认已为当前数据库启用这些选项,请连接到数据库并运行以下 T-SQL 查询:

SELECT database_id,
       name,
       is_accelerated_database_recovery_on,
       is_read_committed_snapshot_on,
       is_optimized_locking_on
FROM sys.databases
WHERE name = DB_NAME();

是否已启用优化锁定?

每个用户数据库都启用了优化锁定。 连接到数据库,然后使用以下查询检查是否启用了优化锁定:

SELECT IsOptimizedLockingOn = DATABASEPROPERTYEX(DB_NAME(), 'IsOptimizedLockingOn');
Result Description
0 已禁用优化锁定。
1 已启用优化锁定。
NULL 优化锁定不可用。

还可以使用 sys.databases 目录视图。 例如,若要查看是否为所有数据库启用了优化的锁定,请执行以下查询:

SELECT database_id,
       name,
       is_optimized_locking_on
FROM sys.databases;

锁定概述

这是未启用优化锁定时行为的简短摘要。 有关详细信息,请查看事务锁定和行版本控制指南

在数据库引擎中,锁定是一种机制,可防止多个事务同时更新相同的数据,以确保事务的 ACID 属性。

当事务需要修改数据时,它会请求锁定数据。 如果数据上没有其他冲突锁,则授予该锁,并且事务可以继续进行修改。 如果数据上保留其他冲突锁,则事务必须等待该锁释放,然后才能继续。

当多个事务试图同时访问相同数据时,数据库引擎必须解决与并发读取和写入相关的潜在复杂冲突。 锁定是引擎可为 ANSI SQL 事务隔离级别提供语义的机制之一。 虽然数据库中的锁定必不可少,但并发性降低、死锁、复杂性和锁开销都会影响性能和可伸缩性。

事务 ID (TID) 锁定

当正在使用基于行版本控制的隔离级别,或已启用 ADR 时,数据库中的每行在内部都包含一个事务 ID (TID)。 此 TID 保留在磁盘上。 每个修改行的事务都会使用其 TID 标记该行。

使用 TID 锁定时,不是对行的键锁定,而是对行的 TID 锁定。 修改事务会在其 TID 上持有 X 锁。 在等到第一个事务完成之前,其他事务在 TID 上获取 S 锁。 使用 TID 锁定时,修改时将继续使用页锁和行锁,但修改每行后,每个页锁和行锁都会释放。 事务结束前唯一持有的锁是 TID 资源上的单个 X 锁,它取代了多个页锁和行(键)锁。

请考虑以下示例,该示例显示了写入事务处于活动状态时当前会话的锁:

/* Is optimized locking is enabled? */
SELECT IsOptimizedLockingOn = DATABASEPROPERTYEX(DB_NAME(), 'IsOptimizedLockingOn');

CREATE TABLE t0
(
a int PRIMARY KEY,
b int NULL
);

INSERT INTO t0 VALUES (1,10),(2,20),(3,30);
GO

BEGIN TRANSACTION;

UPDATE t0
SET b = b + 10;

SELECT *
FROM sys.dm_tran_locks
WHERE request_session_id = @@SPID
      AND
      resource_type IN ('PAGE','RID','KEY','XACT');

COMMIT TRANSACTION;
GO

DROP TABLE IF EXISTS t0;

如果启用了优化锁定,则请求仅在 X(事务)资源上持有单个 XACT 锁。

单个会话的 sys.dm_tran_locks 查询结果集的屏幕截图,在启用优化锁定时仅显示一个锁。

如果未启用优化锁定,则同一个请求将持有四个锁 - 包含这些行的页面上的一个 IX(意向排他)锁,以及每行各有三个 X 键锁:

屏幕截图,显示未启用优化锁定时对单个会话的 sys.dm_tran_locks 查询的结果集显示三个锁。

在检查或排除锁定问题(例如,观察优化锁定的实际操作)时,sys.dm_tran_locks 动态管理视图 (DMV) 很有用。

限定后锁定 (LAQ)

基于 TID 基础结构,优化锁定的 LAQ 组件更改了 DML 语句(如 INSERTUPDATEDELETE)获取锁的方式。

如果没有优化锁定,则需先获取更新 (U) 行锁,从而在扫描中逐行检查查询谓词。 如果满足谓词,则会在更新行之前获取排他 (X) 行锁,并持有到事务结束。

使用优化锁定并且启用 READ COMMITTED 快照隔离级别 (RCSI) 时,系统可以在不获取任何锁的情况下在行的最新提交版本上乐观地检查谓词。 如果谓词不满足,查询将移动至扫描中的下一行。 如果满足谓词,则会获取 X 行锁来更新行。

换句话说,在对要修改的行进行限定后获取锁。 在事务结束前,行更新完成后,就会释放 X 行锁。

由于谓词评估是在不获取任何锁的情况下执行的,因此修改不同行的并发查询不会相互阻止。

例如:

CREATE TABLE t1
(
a int NOT NULL,
b int NULL
);

INSERT INTO t1
VALUES (1,10),(2,20),(3,30);
GO
会话 1 会话 2
BEGIN TRANSACTION;
UPDATE t1
SET b = b + 10
WHERE a = 1;
BEGIN TRANSACTION;
UPDATE t1
SET b = b + 10
WHERE a = 2;
COMMIT TRANSACTION;
COMMIT TRANSACTION;

如果没有优化锁定,则因为会话 1 持有会话 2 需要更新的行上的 U 锁,所以会阻止会话 2。 但使用优化锁定时,因为没有获取 U 锁,并且因为在第 1 行的最新提交版本中,列 a 的值等于 1,这不满足会话 2 的谓词条件,所以不会阻止会话 2。

假设在检查谓词后未修改行,则乐观地执行 LAQ。 如果满足谓词,并且在检查谓词后未修改行,则由当前事务修改该行。

由于未获取 U 锁,因此在评估谓词后并发事务可能会修改该行。 如果某一活动事务持有该行的 X TID 锁,则数据库引擎将等待该事务完成。 如果在先前计算谓词后该行发生更改,则数据库引擎会在修改行之前重新评估(进行重新限定)谓词。 如果仍满足谓词,则修改该行。

查询引擎运算符的子集支持谓词重新限定。 如果需要谓词重新评估,但查询计划使用不支持谓词重新限定的运算符,则数据库引擎在内部中止语句处理,并在没有 LAQ 的情况下重启它。 发生此类中止时,将触发 lock_after_qual_stmt_abort 扩展事件。

某些语句(例如 UPDATE 具有变量赋值的语句和具有 OUTPUT 子句的语句)不能中止和重启,而无需更改其语义。 对于此类语句,不使用 LAQ。

在以下示例中,因为另一个事务已更改该行,所以会重新评估谓词:

CREATE TABLE t3
(
a int NOT NULL,
b int NULL
);

INSERT INTO t3 VALUES (1,10),(2,20),(3,30);
GO
会话 1 会话 2
BEGIN TRANSACTION;
UPDATE t3
SET b = b + 10
WHERE a = 1;
BEGIN TRANSACTION;
UPDATE t3
SET b = b + 10
WHERE a = 1;
COMMIT TRANSACTION;
COMMIT TRANSACTION;

LAQ 启发

限定后锁定 (LAQ) 中所述,使用 LAQ 时,可能会在内部重新启动并处理某些语句,而不使用 LAQ。 如果这种情况经常发生,重复处理的开销可能会变得很大。 为了尽量减少这一开销,优化锁定使用启发式机制来跟踪重复处理。 如果开销超过阈值,此机制将禁用针对数据库的 LAQ 功能。

为了使用启发式机制,语句完成的工作按已处理的页数(逻辑读取)来衡量。 如果数据库引擎正在修改在语句处理开始后被另一个事务修改过的行,则语句执行的操作可能被视为浪费,因为该语句可能会被中止并重新启动。 系统跟踪可能浪费的总工作量和数据库中所有语句完成的总工作量。

如果可能浪费的工作百分比超过某个阈值,则会为数据库禁用 LAQ。 如果重新启动的语句数超过阈值,则 LAQ 也会被禁用。

如果浪费的工作量和重启语句的数量低于其各自的阈值,则会为数据库重新启用 LAQ。

LAQ 局限性

在以下情况中,可能不会使用限定后锁定:

  • LAQ 启发禁用时。
  • 使用了冲突的锁提示(例如,UPDLOCKREADCOMMITTEDLOCKXLOCKHOLDLOCK)时。
  • 当事务隔离级别不是 READ COMMITTED 或者数据库选项 READ_COMMITTED_SNAPSHOT 已禁用时。
  • 当被修改的表具有列存储索引时。
  • 当 DML 语句包含变量赋值时。
  • 当 DML 语句有 OUTPUT 子句时。
  • 当 DML 语句使用多个索引查找或扫描运算符读取正在修改的行时。
  • MERGE 语句中。

使用优化锁定和 RCSI 更改查询行为

启用优化锁定时,读提交快照隔离 (RCSI) 下依赖严格事务执行顺序的并发工作负荷在查询行为方面可能会出现差异。

请考虑以下示例,其中事务 T2 基于事务 T1 期间更新的列 t4 来更新表 b

CREATE TABLE t4
(
a int NOT NULL,
b int NULL
);

INSERT INTO t4
VALUES (1,1);
GO
会话 1 会话 2
BEGIN TRANSACTION T1;
UPDATE t4
SET b = 2
WHERE a = 1;
BEGIN TRANSACTION T2;
UPDATE t4
SET b = 3
WHERE b = 2;
COMMIT TRANSACTION;
COMMIT TRANSACTION;

我们来评估在使用和不使用限定后锁定 (LAQ) 情况下前一个场景的结果。

没有 LAQ

如果不使用 LAQ,则会阻止事务 T2 中的 UPDATE 语句,并等待事务 T1 完成。 T1 完成后,因为已满足其谓词,所以 T2 会更新该行,将列 b 设置为 3

两个事务提交后,表 t4 包含以下行:

 a | b
 1 | 3

使用 LAQ

使用 LAQ 时,事务 T2 使用行的最新提交版本,其中列 b 等于 1,以便评估其谓词 (b = 2)。 该行不符合条件;因此,系统会跳过该行,并且语句完成,不会被事务 T1 阻止。 在此示例中,LAQ 消除了阻塞,但会导致不同的结果。

两个事务提交后,表 t4 包含以下行:

 a | b
 1 | 2

Important

即使没有 LAQ,当使用基于行版本控制的隔离级别时,应用程序也不应假定数据库引擎会在没有使用锁定提示的情况下保证严格排序。 对于在 RCSI 下运行并发工作负载并依赖严格的事务执行顺序(如前面的示例中所示)的客户,我们的一般建议是使用更严格的隔离级别,例如 REPEATABLE READSERIALIZABLE

优化锁定的诊断附加功能

以下改进有助于在启用优化锁定时监控和排查阻止和死锁问题:

  • 优化锁定的等待类型
    • TID 上 XACT 锁的 S 等待类型,以及 sys.dm_os_wait_stats (Transact-SQL) 中的资源描述:
      • LCK_M_S_XACT_READ - 当任务正在等待 XACTwait_resource 类型的共享锁并打算读取时发生。
      • LCK_M_S_XACT_MODIFY - 当任务正在等待 XACTwait_resource 类型的共享锁并打算修改时发生。
      • LCK_M_S_XACT - 当任务正在等待 XACTwait_resource 类型的共享锁且无法推断出意图时发生。 此方案并不常见。
  • 锁定资源可见性
    • XACT 锁定资源。 有关更多信息,请参阅“resource_description 中的 ”。
  • 等待资源可见性
    • XACT 等待资源。 有关更多信息,请参阅“wait_resource 中的 ”。
  • 死锁图
    • 在死锁报告 <resource-list> 中的每个资源下,每个 <xactlock> 元素都会报告死锁中每个成员的锁的基础资源和特定信息。 有关更多信息和示例,请参阅“优化锁定和死锁”。
  • 扩展事件
    • 当一个语句由于与另一个事务冲突而在内部中止并重新启动时,会触发 lock_after_qual_stmt_abort 事件。 有关详细信息,请参阅限定后锁定 (LAQ)
    • 在 SQL Server 2025(17.x) 预览版和 Azure SQL 托管实例中, locking_stats 事件每几分钟在每个数据库中触发一次,并为指定时间间隔提供聚合锁定统计信息,例如锁升级的次数、是否启用了优化锁定的 TID 锁定和 LAQ 组件,以及由于多种原因未使用 LAQ 的查询数量。 即使禁用了优化锁定,也会触发此事件。

优化锁定的最佳做法

启用读提交快照隔离级别 (RCSI)

为了最大程度地提高优化锁定的优势,建议在数据库上启用读提交快照隔离 (RCSI),并使用 READ COMMITTED 隔离作为默认隔离级别。 如果尚未启用 RCSI,请通过连接到 master 数据库并执行以下语句来启用:

ALTER DATABASE [database-name-placeholder] SET READ_COMMITTED_SNAPSHOT ON;

在 Azure SQL 数据库中,默认启用 RCSI,且 READ COMMITTED 是默认隔离级别。 在启用 RCSI 的情况下,使用 READ COMMITTED 隔离级别时,读取器从语句开头创建的快照中读取行的版本。 使用 LAQ,写入器根据行的最新提交版本来限定每个谓词的行,而无需获取 U 锁。 使用 LAQ,只有当行限定并且该行上存在活动的写入事务时,查询才会等待。 根据最新提交的版本进行限定,仅锁定限定行可减少阻塞并提高并发性。

除了减少阻止之外,所需的锁内存也会减少。 这是因为读取器不获取任何锁,而写入器仅获取短期锁,而不是持有到事务结束的锁。 使用更严格的隔离级别(如 REPEATABLE READSERIALIZABLE)时,数据库引擎即使在启用优化锁定的情况下也会对读取操作和写入操作持有行锁和页锁,直到事务结束,这会导致阻塞和锁定内存使用量增加。

避免锁定提示

启用优化锁定时,尽管遵循了表和查询提示(例如,UPDLOCKREADCOMMITTEDLOCKXLOCKHOLDLOCK 等),但会导致无法充分利用优化锁定。 锁提示强制数据库引擎执行行锁或页锁,并持有它们,直到事务结束,以符合锁提示的意图。 有些应用程序的逻辑需要锁提示,例如,使用 UPDLOCK 提示读取行,然后更新行时。 建议仅在需要时使用锁提示。

使用优化锁定时,现有查询没有限制,不需要重写查询。 未使用提示的查询从优化锁定中获益最大。

查询中某个表的表提示不会对同一查询中的其他表禁用优化锁定。 此外,优化锁定仅影响 DML 语句(例如,INSERTUPDATEDELETEMERGE)更新的表的锁定行为。 例如:

CREATE TABLE t5
(
a int NOT NULL,
b int NOT NULL
);

CREATE TABLE t6
(
a int NOT NULL,
b int NOT NULL
);
GO

INSERT INTO t5 VALUES (1,10),(2,20),(3,30);
INSERT INTO t6 VALUES (1,10),(2,20),(3,30);
GO

UPDATE t5 SET t5.b = t6.b
FROM t5
INNER JOIN t6 WITH (UPDLOCK)
ON t5.a = t6.a;

在前面的查询示例中,只有表 t6 会受到锁定提示的影响,而 t5 仍可以利用优化锁定。

UPDATE t5
SET t5.b = t6.b
FROM t5 WITH (REPEATABLEREAD)
INNER JOIN t6
ON t5.a = t6.a;

在前面的查询示例中,只有表 t5 使用 REPEATABLE READ 隔离级别,并且将锁持有到事务结束。 其他更新 t5 仍可以利用优化锁定。 这同样适用于 HOLDLOCK 提示。

常见问题 (FAQ)

新数据库和现有数据库中是否默认启用优化锁定?

在 Azure SQL 数据库中,Microsoft Fabric 预览版中的 Azure SQL 托管实例AUTD 和 SQL 数据库,是的。 在 SQL Server 2025(17.x) 预览版优化锁定默认处于禁用状态,但可在已启用加速数据库恢复的任何用户数据库上启用。

如何检测是否已启用优化锁定?

请参阅 是否已启用优化锁定?

如果我想在优化锁定的情况下强制阻止查询,该怎么办?

如果启用了 RCSI,请使用 READCOMMITTEDLOCK 表提示,在启用优化锁定时强制两个查询之间发生阻止。

在只读次要副本上是否使用了优化锁定?

否,因为 DML 语句无法在只读副本上运行,因此不会获取相应的行锁和页锁。

修改 tempdb 和临时表中的数据时是否使用了优化锁定?

目前没有。