您的位置: 首页> 数据库> SQL Server 2025中解决“写写阻塞”的利器

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

时间:2025-09-01 14:52:15 来源:互联网

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 版本体现了对数据库引擎的重大投入。



优化锁定

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

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

并发模型和锁模式

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

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

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

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

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

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

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

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

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



背后的技术

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

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

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

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

use mastergo-- 加速数据库恢复(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());

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

事务 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 testgoDROP 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 testgo-- 通过 sys.dm_tran_locks 检查更新行上的锁BEGIN TRANSACTION;UPDATE  dbo.SensorReadingsSET  ReadingValue = ReadingValue + 10;SELECT  *FROM  sys.dm_tran_locksWHERE  request_session_id = @@SPIDAND  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 testgoDROP 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 testgoselect @@SPIDBEGIN TRANSACTION;UPDATEdbo.EntityCountersSETCounterValue = CounterValue + 10WHEREEntityID = 2;--会话二--如果没有开启读提交快照RCSI隔离级别,那么这个语句会被阻塞,use testgoselect @@SPIDBEGIN TRANSACTION;UPDATEdbo.EntityCountersSETCounterValue = CounterValue + 10WHEREEntityID = 1; --会话三use testgoSELECTresource_type,resource_database_id,resource_description,request_mode,request_type,request_status,request_session_id,resource_associated_entity_idFROMsys.dm_tran_locksWHERErequest_session_id IN(72, 73)  --代入实际的会话IDAND 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)机制减少了锁管理所需的内存消耗,并通过降低并发事务之间的锁竞争,最大限度地减少了锁升级情况的发生。


 

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

 

上一篇:拒绝试错成本!企业接入MCP协议的避坑清单​ 下一篇:MySQL 29 如何判断一个数据库是不是出问题了?

相关文章

相关应用

最近更新