SQL Server 2025中解决“写写阻塞”的利器

SQL Server 2025中解决"写写阻塞"的利器

最近安装了SQL Server 2025的CTP 2.0 版本,做了一些测试

SQLServer 2025安装包获取

通过网盘分享的文件:SQLServer 2025-x64-ENU.iso

链接: https://pan.baidu.com/s/1vs4QIwynJqzGlh0uVtBZWw?pwd=uyzn

提取码: uyzn

背景

在当今的应用场景中,可扩展性和并发性已成为核心需求,如何在保持数据一致性的同时提供高性能是一项复杂的挑战。SQL Server 2025 对数据库引擎进行了多项增强,以提升性能和可扩展性。

"Optimized Locking"------ 这个拥有奇妙名称的功能改变了 DML 语句获取锁的方式。该功能最初在 云上Azure SQL 中推出,如今已在 本地SQL Server 2025 中可用。

数据库引擎增强

SQL Server 2025 版本体现了对数据库引擎的重大投入。

  • 在 Tempdb 系统数据库上默认启用加速数据库恢复(ADR)功能。涉及临时表、表变量或在 Tempdb 中创建的非临时表的事务可能会受到回滚和过度事务日志使用的负面影响。在 Tempdb 中启用 ADR 后,即使是影响 tempdb 对象的事务,也能实现事务日志的即时截断。

  • 为 Linux 上的 SQL Server 在 tmpfs文件系统上启用Tempdb。

  • 强化了 "可读辅助副本持久化统计信息" 功能,该功能最初在 SQL Server 2022 中引入,但默认处于禁用状态,需要启用跟踪标志 12606 才能使用。在 SQL Server2025 中,可读副本的查询存储默认启用。

优化锁定

优化锁定是 SQL Server 2025 引擎中的一项新功能。它旨在减少锁管理所用的内存、减少锁升级的发生,并提高数据库的并发性。

在深入了解优化锁定的工作原理之前,有必要先了解并发模型和锁定机制。

并发模型和锁模式

锁定是一种防止多个事务同时修改相同数据的机制,从而确保事务符合 ACID 属性。

当事务需要修改数据时,引擎会请求对目标数据加锁。只有当同一数据集上没有不兼容的锁时,该锁才能被授予,允许事务继续执行更新。

如果已存在其他锁,事务必须等待该锁释放后才能继续。锁定是数据库引擎根据所选隔离级别强制执行事务隔离的机制之一。 SQL Server 数据库引擎支持两种并发模型:

  • 悲观并发模型
  • 乐观并发模型

在悲观模型中,读事务获取的锁会阻塞尝试获取写锁的事务,反之亦然。在乐观模型中,读事务获取的锁不会阻塞写锁,但写锁之间仍然不兼容

每个事务都指定一个隔离级别,该级别决定了它与其他事务的隔离程度。

在SQL Server里,对于悲观并发模型,有四个隔离级别:

  • 未提交读
  • 已提交读
  • 可重复读
  • 序列化

对于乐观并发模型,有两个隔离级别:

  • 快照
  • 已提交读快照

并非所有人都知道,已提交读快照并非一个独立的隔离级别,而是当启用 READ_COMMITTED_SNAPSHOT 选项时,对已提交读隔离级别的行为修改。

根据所选的隔离级别,数据库引擎会使用不同的锁模式锁定资源,这些锁模式决定了并发事务对这些资源的访问方式(或限制)。

背后的技术

优化锁定依赖于两项早已集成到数据库引擎中的技术。

  1. 第一项是SQL Server 2019推出的加速数据库恢复(ADR) ,它是启用优化锁定的必要前提。加速数据库恢复必须在数据库级别启用,它通过加快事务回滚操作和更高效地处理未提交的更改来改进事务恢复。

  2. 第二项技术是已提交读快照隔离(RCSI) 。虽然并非严格要求,但启用 RCSI 可以充分发挥优化锁定的优势。

以下 T-SQL 代码开启加速数据库恢复、已提交读快照和优化锁定功能,并且检查当前的开启状态。

复制代码
use master
go

-- 加速数据库恢复(ADR)
ALTER DATABASE test SET ACCELERATED_DATABASE_RECOVERY = ON;

--已提交读快照隔离级别(RCSI)以获得最大收益
ALTER DATABASE test  SET READ_COMMITTED_SNAPSHOT ON;  --不是必须

--开启优化锁定
ALTER DATABASE test SET OPTIMIZED_LOCKING = ON;



--检查开启状态
SELECT
  IsOptimizedLockingOn = DATABASEPROPERTYEX(DB_NAME(), 'IsOptimizedLockingOn')
  ,RCSI = is_read_committed_snapshot_on
  ,ADR  = is_accelerated_database_recovery_on
 FROM
   sys.databases
 WHERE
   (name = DB_NAME());

优化锁定基于两项关键机制:

  • 1、事务 ID(TID)锁 (Transaction ID (TID) locking)

  • 2、验证后锁定(LAQ) (Lock After Qualification (LAQ))

事务 ID(TID)锁

相较于之前的行锁(key或者rid)、页锁、表锁,新增了一种锁资源类型XACT,解决锁升级之后的更严重的阻塞问题。 当使用已提交读快照隔离级别和启用加速数据库恢复(ADR)后,数据库中的每行数据内部都包含一个事务 ID(TID)。当事务修改某行数据时,会将自身的TID分配给该行。

在 TID 锁定机制中,锁并非获取在行的key或者rid上,而是获取在行的 TID 上。修改数据行的事务会对自身的 TID 持有排他(X)锁。其他需要修改同一行数据的事务会对该 TID 获取共享(S)锁,以等待第一个事务(update该行数据的事务)完成。

通过 TID 锁,第一个事务修改过程中仍会获取页锁和行锁,但这些页锁和行锁在数据行更改完成后会立即释放,即使事务尚未提交或回滚。事务结束前唯一持有的锁是对 TID 资源的单个排他(X)锁,它有效地替代了多个页锁和行锁(key或者rid)。

在启用了优化锁定的 SQL Server 2025 数据库中执行。

示例表 dbo.SensorReadings 包含两列:

SensorID(整数类型,为主键)

ReadingValue(整数类型,用于存储示例值)。

复制代码
use test
go

DROP TABLE IF EXISTS dbo.SensorReadings;

CREATE TABLE dbo.SensorReadings
(
  SensorID INTEGER PRIMARY KEY NOT NULL,
  ReadingValue INTEGER NOT NULL
);

INSERT INTO dbo.SensorReadings VALUES (1, 10), (2, 20), (3, 30);
GO

我们将更新 ReadingValue 中存储的值,同时使用动态管理视图 sys.dm_tran_locks 监控获取的锁。

复制代码
use test
go

-- 通过 sys.dm_tran_locks 检查更新行上的锁
BEGIN TRANSACTION;

UPDATE
  dbo.SensorReadings
SET
  ReadingValue = ReadingValue + 10;

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

COMMIT TRANSACTION;
GO

观察到的唯一锁是在 TID 上获取的锁,并且只有一个TID锁,如下方图片所示。

而在禁用了优化锁定的数据库中对同一表执行相同操作时,会显示符合条件的3个数据行上的排他(X)锁以及包含这些数据行的数据页上的意向排他(IX)锁。这些锁会保持到事务完成,如下方图片所示。

可以看到,明明要申请三行数据上的三个排它锁,现在只申请了一个事务锁,大大降低了锁升级导致的阻塞问题的发生。

验证后锁定(LAQ)-》需要打开(读提交快照隔离级别)

DML 操作变慢的主要原因之一,是在搜索满足查询条件的数据行过程中获取锁。SQL Server 2025 中的 "验证后锁定" 改变了 DML 语句(UPDATE、DELETE、MERGE)获取锁的方式。

当未启用优化锁定(Optimized Locking )时,查询会在扫描过程中逐行评估谓词。首先会对每行获取更新(U)锁,若谓词条件满足,则在更新行之前立即将其转换为排他(X)锁。排他(X)锁会保持到事务完成。

当启用优化锁定(Optimized Locking )时,会在不获取任何锁的情况下,基于行的最新已提交版本评估谓词。若谓词条件不满足,扫描会继续处理下一行;若谓词条件满足,则会获取排他(X)锁来执行更新。此锁会在行更新完成后、事务结束前立即释放。

由于谓词评估过程无需加锁,修改不同的数据行的并发查询不会相互阻塞。

在启用了优化锁定(Optimized Locking )的 SQL Server 2025 数据库中执行下面语句

复制代码
use test
go

DROP TABLE IF EXISTS dbo.EntityCounters;

CREATE TABLE dbo.EntityCounters
(
  EntityID INTEGER NOT NULL,
  CounterValue INTEGER NULL
);

INSERT INTO dbo.EntityCounters VALUES (1, 10), (2, 20), (3, 30);
GO

我们假设有两个并发查询,在两个不同的会话中按照顺序执行。 先执行会话一,再执行会话二,最后执行会话三

复制代码
--会话一
use test
go

select @@SPID

BEGIN TRANSACTION;
UPDATE
dbo.EntityCounters
SET
CounterValue = CounterValue + 10
WHERE
EntityID = 2;






--会话二
--如果没有开启读提交快照RCSI隔离级别,那么这个语句会被阻塞,

use test
go


select @@SPID


BEGIN TRANSACTION;
UPDATE
dbo.EntityCounters
SET
CounterValue = CounterValue + 10
WHERE
EntityID = 1; 






--会话三
use test
go


SELECT
resource_type
,resource_database_id
,resource_description
,request_mode
,request_type
,request_status
,request_session_id
,resource_associated_entity_id
FROM
sys.dm_tran_locks
WHERE
request_session_id IN
(72, 73)  --代入实际的会话ID
AND resource_type IN
('PAGE', 'RID', 'KEY', 'XACT')
order by request_session_id;
  • 开启了优化锁定的情况

两个会话各自申请了一个事务锁,而且互相不会阻塞

  • 没有开启优化锁定的情况

如果在没有开启优化锁定(Optimized Locking )的数据库上执行,会发现会话二正在等待会话一在EntityID = 1; 这行数据上的U锁 ,导致被阻塞。

当 UPDATE 查询扫描表时,会首先对每个检查的行获取更新(U)锁,以确保没有其他事务能同时修改该行。 若 WHERE 子句中的谓词条件满足,更新(U)锁会转换为排他(X)锁并更新行;若谓词条件不满足,更新(U)锁会被释放且不修改行,更新(U)锁会一直保持到事务结束。

另一个查询(会话 2)尝试更新 跟(会话 1)非同一行数据,即使第一个查询(会话 1)最终并未修改受更新(U)锁影响的行(EntityID = 1这行数据),(会话 2) 也必须等待会话 1 释放更新(U)锁。

这时候只能读取EntityID = 1这行数据,但是不能对这行数据做更新。

关于更新(U)锁和共享(S)锁兼容如下图所示:

总结

SQL Server 2025 和 Azure SQL Database 中的优化锁技术,是并发管理领域的一项重大进步。 借助事务 ID(TID)锁和验证后锁定(LAQ)机制减少了锁管理所需的内存消耗,并通过降低并发事务之间的锁竞争,最大限度地减少了锁升级情况的发生。

本文版权归作者所有,未经作者同意不得转载。

相关推荐
2501_916013743 小时前
iOS 文件管理与 uni-app 性能优化实战 多工具协作的完整指南
android·ios·性能优化·小程序·uni-app·iphone·webview
拾忆,想起5 小时前
Redis红锁(RedLock)解密:分布式锁的高可用终极方案
java·数据库·redis·分布式·缓存·性能优化·wpf
wayhome在哪21 小时前
3 分钟上手!用 WebAssembly 优化前端图片处理性能(附完整代码)
javascript·性能优化·webassembly
国家不保护废物1 天前
10万条数据插入页面:从性能优化到虚拟列表的终极方案
前端·面试·性能优化
拾忆,想起1 天前
Redis发布订阅:实时消息系统的极简解决方案
java·开发语言·数据库·redis·后端·缓存·性能优化
风铃喵游1 天前
前端内存优化:String 鲜为人知的一些事
前端·性能优化
励志成为糕手1 天前
Java线程池深度解析:从原理到实战的完整指南
java·开发语言·性能优化·线程池·拒绝策略
DemonAvenger1 天前
MySQL锁机制深度解析:从共享锁到排他锁的技术剖析与实战经验
数据库·mysql·性能优化