为何单独执行SELECT查询时无法看到SQL Server共享锁?
SQL Server SELECT查询未观测到共享锁的原因
问题背景
测试表结构
CREATE TABLE [dbo].[Events] ( [sequenceNumber] [bigint] IDENTITY(1,1) NOT NULL, [event] [varbinary](max) NULL ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] GO
测试现象
- 单独执行读事务无S锁
执行以下事务后,通过sys.dm_tran_locks和sp_lock均未检测到共享锁(S锁):
BEGIN TRANSACTION; SELECT TOP (1) sequenceNumber, event FROM Events ORDER BY sequenceNumber DESC;
- 并发读写时出现S锁
先执行未提交的写入事务:
BEGIN TRANSACTION; INSERT INTO Events (event) VALUES (CONVERT(varbinary, 'Two'))
再执行读事务:
BEGIN TRANSACTION; SELECT TOP (1) sequenceNumber, event FROM Events ORDER BY sequenceNumber DESC;
此时通过sp_lock可观测到排他锁(X锁)与共享锁(S锁)。
疑问
为何单独执行SELECT时看不到S锁?SQL Server是否在该场景下采用无锁读取?
核心原因
这是SQL Server默认读取提交(Read Committed)隔离级别的特性导致的:
- 无并发时的锁行为
在读取提交级别下,SQL Server默认会采用两种优化逻辑:
- 若数据库开启了读取提交快照隔离(RCSI),SELECT会读取数据的行版本,完全不需要获取S锁;
- 即使未开启RCSI,SQL Server也会采用“语句级锁释放”:SELECT执行时会临时获取S锁,但语句执行完毕后立即释放锁——由于锁持有时间极短,你几乎无法通过
sys.dm_tran_locks或sp_lock捕获到。
- 并发场景下的锁行为
当存在未提交的写入事务持有X锁时,若SELECT需要读取被锁定的行(比如你取最新插入的行):
- 若未开启RCSI,SQL Server无法通过行版本获取一致数据,会主动请求S锁并等待X锁释放,此时锁持有时间足够长,能被工具检测到;
- 若开启了RCSI,SELECT会直接读取该行的快照版本,不会请求S锁,也就看不到S锁。
验证方式
若想强制观测到S锁,可通过以下两种方式:
- 修改事务隔离级别为可重复读:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; BEGIN TRANSACTION; SELECT TOP (1) sequenceNumber, event FROM Events ORDER BY sequenceNumber DESC; -- 此时查询sys.dm_tran_locks即可看到S锁
- 关闭数据库的读取提交快照隔离:
ALTER DATABASE YourDatabaseName SET READ_COMMITTED_SNAPSHOT OFF; -- 之后在默认隔离级别下执行读事务,就能观测到S锁
内容的提问来源于stack exchange,提问作者Pragun
相关产品推荐
相关产品推荐

