You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

SQL Server中S Key锁未释放引发死锁问题求助

拆解你的Key死锁问题:根因分析与解决方案

我来帮你梳理这个困扰你的死锁问题,结合你提供的日志和排查信息,咱们一步步来分析:

核心异常表现

当1个进程执行DELETE、2个进程执行SELECT时触发了Key死锁,最关键的异常点是:执行SELECT的进程会留下未释放的S Key锁——哪怕是空表都会出现这个情况,这个残留的锁就是死锁的导火索。

空表场景下的锁日志能清晰看到这个问题:

66 acquiring IS lock on OBJECT: 6:1614680850:0 (class bit0 ref1) result: OK
Process 66 acquiring IS lock on PAGE: 6:1:16596 (class bit0 ref1) result: OK
Process 66 acquiring S lock on KEY: 6:72057594126925824 (fca2a895105d) (class bit1000000 ref1) result: OK
Process 66 releasing lock on PAGE: 6:1:16596 (0 row(s) affected) (1 row(s) affected)
Process 66 releasing lock on OBJECT: 6:1614680850:0
该S Key锁:6:72057594126925824 (fca2a895105d) (class bit1000000 ref1)未释放

你已经排查到的关键线索

这些线索对定位问题非常重要:

  • 重建Key索引后,这个残留的S锁就消失了
  • 只要删除表中任意一条数据,之后每次SELECT都会复现这个锁
  • 表上只有一个基于Id的聚集索引,隔离级别是Read Committed

你的聚集索引定义

CONSTRAINT [PK-404] PRIMARY KEY CLUSTERED ( [id] ASC )
WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, 
      IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) 
ON [PRIMARY]

死锁日志的细节解析

从你提供的死锁XML来看,这是典型的循环等待死锁

  1. 执行SELECT的进程(process42dfb48c8)持有了某个Key的S锁,同时等待另一个Key的S锁
  2. 执行DELETE的进程(process4021d5088)持有了另一个Key的X锁,同时等待SELECT进程持有的Key的X锁
  3. 双方都握着对方需要的锁资源,谁也不让谁,最终触发死锁,其中SELECT进程被选为牺牲品

根因判断与解决办法

根因是什么?

这个问题几乎可以确定是聚集索引出现了元数据损坏或统计信息异常。在Read Committed隔离级别下,正常的SELECT查询只会在读取数据时临时持有S锁,读取完成后就会释放。而空表或删除数据后仍残留锁,说明SQL Server在处理索引时,错误地认为某个Key资源存在,从而锁定了它,而且没有正常释放。

已经验证有效的解决办法

你已经发现重建索引能解决问题,这是最直接的修复方式,执行以下SQL即可:

ALTER INDEX [PK-404] ON [SYNCHRONIZATION_STATUS] REBUILD;

预防复发的建议

  1. 定期更新统计信息:如果之后问题再次出现,先更新表的统计信息,确保SQL Server能生成正确的执行计划:
    UPDATE STATISTICS [SYNCHRONIZATION_STATUS];
    
  2. 监控索引碎片:定期检查索引碎片率,当碎片率超过30%时重建索引,低于30%可以选择重新组织:
    -- 查询索引碎片情况
    SELECT 
        name AS 索引名称,
        avg_fragmentation_in_percent AS 碎片率
    FROM sys.dm_db_index_physical_stats(
        DB_ID('KHI-SP11-Rel-0103'),
        OBJECT_ID('SYNCHRONIZATION_STATUS'),
        NULL,
        NULL,
        'DETAILED'
    );
    
  3. 优化查询与隔离级别:对于这种频繁DELETE和SELECT的场景,可以考虑:
    • 给SELECT查询创建覆盖索引,减少锁的范围
    • 开启Read Committed Snapshot隔离级别(RCSI),利用行版本控制避免锁冲突

额外的调试工具

如果想实时监控锁的持有情况,可以用这个查询:

SELECT 
     request_session_id AS 会话ID,
     resource_type AS 资源类型,
     resource_description AS 资源描述,
     request_mode AS 锁模式,
     request_status AS 请求状态
FROM sys.dm_tran_locks
WHERE resource_database_id = DB_ID('KHI-SP11-Rel-0103');

内容的提问来源于stack exchange,提问作者Artsiom Che

火山引擎 最新活动