适用于: SQL Server 2025 (17.x)
Azure SQL 数据库
Azure SQL 托管实例
Microsoft Fabric 中的 SQL 数据库
优化锁定提供了改进的事务锁定机制,以减少并发事务的锁定阻塞和锁定内存消耗。
什么是优化锁定?
因为即使是大型事务持有的锁也很少,所以优化锁定有助于减少锁内存。 此外,优化的锁定可避免锁升级,并可以避免某些类型的死锁。 这允许对表进行更多并发访问。
优化锁定由两个主要部分组成:事务 ID (TID) 锁定和限定后锁定 (LAQ)。
- 事务 ID (TID) 是事务的唯一标识符。 每一行都标有修改它的最后一个 TID。 TID 上的单个锁用于保护所有修改的行,而不是可能有多个键或行标识符锁。 有关详细信息,请参阅事务 ID (TID) 锁定。
- 限定后锁定 (LAQ) 是一种优化,它在不获取锁定的情况下,使用行的最新提交版本评估查询谓词,从而提高并发性。 LAQ 需要读取提交的快照隔离(RCSI)。 有关详细信息,请参阅限定后锁定 (LAQ)。
例如:
- 如果没有优化锁定,更新表中的 1,000 行可能需要持有 1,000 个排他 (
X) 行锁,直到事务结束。 - 通过优化锁定,更新表中的 1,000 行可能确实需要 1,000 个
X行锁,但在更新每行时,锁都会立即释放。同时,只有一个XTID 锁会保留到事务结束。 由于很快就会释放锁,因此会减少锁内存使用量,并且发生锁升级的可能性要小得多,从而提高工作负荷并发性。
Note
启用优化锁定可以减少或消除数据修改语言 (DML) 语句(例如 INSERT、UPDATE、DELETE、MERGE)获取的行锁和页锁。 这不会影响其他类型的数据库和对象锁,例如架构锁。
Availability
下表汇总了跨 SQL 平台优化锁定的可用性和启用状态。
| Platform | Available | 默认启用 |
|---|---|---|
| Azure SQL 数据库 | Yes | 是(始终启用) |
| Microsoft Fabric 中的 SQL 数据库 | Yes | 是(始终启用) |
| Azure SQL 托管实例AUTD | Yes | 是(始终启用) |
| Azure SQL 托管实例2025 | 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 组件才会生效。
ADR 始终在 Azure SQL 数据库、Azure SQL 托管实例和 Microsoft Fabric 中的 SQL 数据库中启用。 RCSI 默认在 Azure SQL 数据库和 Microsoft Fabric 中的 SQL 数据库中启用。
若要确认已为当前数据库启用这些选项,请连接到数据库并运行以下 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 DATABASEPROPERTYEX(DB_NAME(), 'IsOptimizedLockingOn') AS is_optimized_locking_enabled;
| 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 DATABASEPROPERTYEX(DB_NAME(), 'IsOptimizedLockingOn') AS is_optimized_locking_enabled;
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 锁。
如果未启用优化锁定,则同一个请求将持有四个锁 - 包含这些行的页面上的一个 IX(意向排他)锁,以及每行各有三个 X 键锁:
sys.dm_tran_locks动态管理视图(DMV)可用于检查或排查锁定问题。 在这里,它用于观察优化锁定的运行情况。
限定后锁定 (LAQ)
基于 TID 基础结构,优化锁定的 LAQ 组件更改了 DML 语句(如 INSERT、UPDATE 和 DELETE)获取锁的方式。
如果没有优化锁定,则需先获取更新 (U) 行锁,从而在扫描中逐行检查查询谓词。 如果满足谓词,则会在更新行之前获取排他 (X) 行锁,并持有到事务结束。
通过优化的锁定,以及启用快照隔离级别(RCSI)时 READ COMMITTED ,可以在行的最新提交版本上乐观地检查谓词,而无需获取任何锁。 如果谓词不满足,查询将移动至扫描中的下一行。 如果满足谓词,则会获取 X 行锁来更新行。
换句话说,在对要修改的行进行限定后获取锁。 在事务结束前,行更新完成后,就会释放 X 行锁。
由于谓词评估是在不获取任何锁的情况下执行的,因此修改不同行的并发查询不会相互阻止。
例如:
/* Confirm that optimized locking and read committed snapshot isolation (RCSI) are both enabled on this database. */
SELECT database_id,
name,
is_accelerated_database_recovery_on,
is_optimized_locking_on,
is_read_committed_snapshot_on
FROM sys.databases
WHERE name = DB_NAME();
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 t1SET b = b + 10WHERE a = 1; |
|
BEGIN TRANSACTION;UPDATE t1SET b = b + 10WHERE 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 t3SET b = b + 10WHERE a = 1; |
|
BEGIN TRANSACTION;UPDATE t3SET b = b + 10WHERE a = 1; |
|
COMMIT TRANSACTION; |
|
COMMIT TRANSACTION; |
跳过索引锁 (SIL)
通过 TID 锁定,使用短时间排他行锁(X)和意向排他页锁(IX)来修改行。 使用 RCSI 和 LAQ 时,仅当存在访问该行并期望该行保持稳定的其他查询时,这些锁才是必需的。 例如,此类查询包括那些在REPEATABLE READ或SERIALIZABLE隔离级别下运行的查询,或使用相应锁定提示的查询。 此类查询称为 行锁定查询 (RLQ)。
如果没有访问行的 RLQ 查询,数据库引擎可以在修改行时跳过执行行锁和页锁,并且仅使用独占页 闩锁。 此优化可降低锁定开销,同时保留 ACID 事务语义。 跳过行锁和页锁尤其有利于修改大量行的事务。
目前,SIL 优化仅在以下情况下使用:
-
INSERT堆上的语句。-
IX会跳过页锁。
-
-
UPDATE对聚集索引、非聚集索引和堆的语句。- 将跳过
IX页锁和X行锁。
- 将跳过
SIL 优化目前在以下情况下未使用:
-
DELETE语句。 -
UPDATE如果行包含现有的转发指针,或者更新添加了新的转发指针,则对堆进行语句处理。 - 如果修改后的行具有任何使用 LOB 数据类型的列,例如
varchar(max),nvarchar(max),varbinary(max),和json。 - 对于在同一事务中被拆分的页面上的行。
LAQ 启发
如 在限定后锁定(LAQ)中所述,在使用 LAQ 时,使用不支持谓词重新资格的查询运算符的语句可能会在内部重启并处理,而无需 LAQ。 如果这种情况经常发生,则重新处理开销可能会变得很大。 为了最大程度地减少开销,优化的锁定机制使用启发式反馈机制,如果开销超过阈值,则禁用 LAQ。
出于反馈机制的目的,语句完成的工作以逻辑读取数来衡量。 如果数据库引擎正在修改在语句处理开始后由另一个事务修改的行,则语句完成的工作被视为可能浪费的,因为可能需要重新处理该语句。
执行语句时,数据库引擎会维护 LAQ 反馈数据,该数据跟踪可能浪费的工作、语句重新处理次数以及可能重新处理的语句完成的总工时。
如果可能浪费的工作与总工时的比例,或者重新处理的语句数与语句总数的比率超过其各自的阈值,则 LAQ 将被禁用。 如果这两个比率都低于阈值,则重新启用 LAQ。
LAQ反馈数据被分为两个层级进行跟踪:
对于 查询计划。
- 数据库引擎开始跟踪有关语句重新处理的首次出现的计划的 LAQ 反馈。
- 如果在 查询存储中捕获查询,则查询存储中也会捕获 LAQ 反馈。 数据库引擎使用此反馈在数据库重启时为计划保留 LAQ 启用或禁用状态。
- 具有捕获 LAQ 反馈的查询计划在
plan_id目录视图中具有匹配值的行。feature_id列和feature_desc列分别设置为4和LAQ Feedback。
对于 数据库。
- 对于所有没有查询计划级别反馈的语句,反馈会被聚合。这包括例如在查询存储中未捕获的查询。
- 自数据库启动以来,反馈会被跟踪,并在每次启动后重新创建。
确定是否对语句使用 LAQ 时,系统将使用查询计划反馈(如果可用)。 否则,它使用数据库级别反馈。 这意味着某些语句可能使用 LAQ 执行,有些语句可能在没有 LAQ 的情况下执行。 例如,可能会对查询计划禁用 LAQ,但为数据库启用,反之亦然。
LAQ 局限性
在以下情形中不适用资格后锁定:
- 由 LAQ 启发禁用时。
- 使用了冲突的锁提示(例如,
UPDLOCK、READCOMMITTEDLOCK、XLOCK或HOLDLOCK)时。 - 当事务隔离级别不是
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 t4SET b = 2WHERE a = 1; |
|
BEGIN TRANSACTION T2;UPDATE t4SET b = 3WHERE 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 READ 和 SERIALIZABLE。
优化锁定的诊断附加功能
以下改进有助于在启用优化锁定时监控和排查阻止和死锁问题:
- 优化锁定的等待类型
-
XACT等待 TID 上锁的类型,以及S中的资源说明:-
LCK_M_S_XACT_READ- 当任务正在等待XACTwait_resource类型的共享锁并打算读取时发生。 -
LCK_M_S_XACT_MODIFY- 当任务正在等待XACTwait_resource类型的共享锁并打算修改时发生。 -
LCK_M_S_XACT- 当任务正在等待XACTwait_resource类型的共享锁且无法推断出意图时发生。 此方案并不常见。
-
-
- 锁定资源可见性
-
XACT锁定资源。 有关详细信息,请参阅resource_descriptionsys.dm_tran_locks。
-
- 等待资源可见性
-
XACT等待资源。 有关详细信息,请参阅wait_resourcesys.dm_exec_requests。
-
- 死锁图
- 在死锁报告
<resource-list>中的每个资源下,每个<xactlock>元素都会报告死锁中每个成员的锁的基础资源和特定信息。 有关更多信息和示例,请参阅“优化锁定和死锁”。
- 在死锁报告
- 扩展事件
- 当语句因与另一个事务冲突而在
lock_after_qual_stmt_abort中被重新处理时,该事件会被触发。 有关详细信息,请参阅限定后锁定 (LAQ)。 - 该
locking_stats事件每隔几分钟针对每个数据库触发一次,并为时间间隔提供聚合锁定统计信息,例如锁升级数、是否启用了优化锁定的 TID 锁定和 LAQ 组件,以及出于各种原因未使用 LAQ 的查询数。 即使禁用了优化锁定,也会触发此事件。 - 在 SQL Server 和 Azure SQL 托管实例中,
locking_stats2事件每隔几分钟触发一次,并提供时间间隔的 跳过索引锁 和 LAQ 启发式 统计信息。
- 当语句因与另一个事务冲突而在
优化锁定的最佳做法
启用读提交快照隔离级别 (RCSI)
为了最大限度地提高优化锁定的效益,建议在数据库上启用读取提交快照隔离(RCSI),并将READ COMMITTED隔离设为默认隔离级别。
在 Microsoft Fabric 中的 Azure SQL 数据库和 SQL 数据库中,默认情况下启用 RCSI,并且 READ COMMITTED 是默认隔离级别。 在启用 RCSI 的情况下,使用 READ COMMITTED 隔离级别时,读取器从语句开头创建的快照中读取行的版本。 使用 LAQ,写入器根据行的最新提交版本来限定每个谓词的行,而无需获取 U 锁。 使用 LAQ,只有当行限定并且该行上存在活动的写入事务时,查询才会等待。 根据最新提交的版本进行限定,仅锁定限定行可减少阻塞并提高并发性。
避免锁定提示
启用优化锁定时,尽管遵循了表和查询提示(例如,UPDLOCK、READCOMMITTEDLOCK、XLOCK、HOLDLOCK 等),但会导致无法充分利用优化锁定。 锁提示强制数据库引擎执行行锁或页锁,并持有它们,直到事务结束,以符合锁提示的意图。 有些应用程序的逻辑需要锁提示,例如,使用 UPDLOCK 提示读取行,然后更新行时。 建议仅在需要时使用锁提示。
使用优化锁定时,现有查询没有限制,不需要重写查询。 未使用提示的查询从优化锁定中获益最大。
查询中某个表的表提示不会对同一查询中的其他表禁用优化锁定。 此外,优化锁定仅影响 DML 语句(例如,INSERT、UPDATE、DELETE 或 MERGE)更新的表的锁定行为。 例如:
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 数据库中,azure SQL 托管实例AUTD 和 Microsoft Fabric 中的 SQL 数据库,是的。 在 SQL Server 2025(17.x) 优化锁定默认处于禁用状态,但可以在已启用加速数据库恢复的任何用户数据库上启用。
如何检测是否已启用优化锁定?
请参阅 是否已启用优化锁定?
如果我想在优化锁定的情况下强制阻止查询,该怎么办?
如果启用了 RCSI,请使用 READCOMMITTEDLOCK 表提示,在启用优化锁定时强制两个查询之间发生阻止。
在只读次要副本上是否使用了优化锁定?
否,因为 DML 语句无法在只读副本上运行,因此不会获取相应的行锁和页锁。
修改 tempdb 和临时表中的数据时是否使用了优化锁定?
目前没有。