应查 sys.dm_tran_locks 确认聚合查询是否持有 KEY 或 PAGE 级锁,重点关注 resource_type(KEY/PAGE)、request_mode(S/U/X)及对应会话的事务状态和阻塞源头。

查 sys.dm_tran_locks 看聚合查询是否持有 KEY 或 PAGE 级锁
聚合查询(如 SUM、COUNT、GROUP BY)本身不修改数据,但若在未提交事务中执行,或与 SELECT ... FOR UPDATE 类语义混用(比如嵌套在显式事务里),仍可能升级并持有锁。重点不是“它该不该锁”,而是“它现在锁了什么”。
运行以下语句快速定位:
SELECT
resource_type,
resource_database_id,
resource_associated_entity_id,
request_mode,
request_status,
request_session_id
FROM sys.dm_tran_locks
WHERE resource_database_id = DB_ID()
AND request_session_id IN (
SELECT session_id FROM sys.dm_exec_requests
WHERE command LIKE '%SELECT%' AND sql_handle IS NOT NULL
)
关注 resource_type 是否为 KEY、PAGE 或 OBJECT;request_mode 是否为 S(共享锁)或更重的 U/X。如果看到大量 KEY 锁且 request_session_id 对应一个长时间运行的 GROUP BY 查询,说明它正阻塞写入——这通常是因为查询扫描了带非覆盖索引的宽表,触发了键查找+逐行加锁。
用 sys.dm_exec_requests + sys.dm_exec_sql_text 抓住正在等锁的聚合语句
聚合查询卡住,往往不是自己慢,而是被别的事务堵住了读路径(比如另一个会话持有了 X 锁没释放)。此时它会出现在 sys.dm_exec_requests 中,wait_type 显示为 LCK_M_S、LCK_M_U 等。
-
wait_type是LCK_M_S:说明它想加共享锁,但资源上已有不兼容锁(比如别人持X锁未提交) -
wait_resource值形如KEY: 5:72057594038845440 (98ec012aa510):可直接对应到sys.dm_tran_locks中的resource_type和resource_associated_entity_id -
blocking_session_id > 0:立刻查那个会话在干啥,用sys.dm_exec_sql_text提取它的 SQL
别只看聚合语句本身,要顺藤摸瓜查阻塞源头——常是上游一个未提交的 UPDATE 或 DELETE,让后续所有读都卡在锁队列里。
检查隔离级别和查询写法是否无意放大锁范围
默认 READ COMMITTED 下,聚合查询一般只在扫描时短暂持锁;但以下情况会让锁持续更久、范围更大:
- 会话设置了
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ或SERIALIZABLE:前者会让所有读过的 KEY 持锁到事务结束;后者甚至锁范围(Range Lock),影响插入 - 聚合查询里用了
ORDER BY且排序列无索引:SQL Server 可能需要先建临时工作表再聚合,过程中对 tempdb 加闩锁,间接拖慢整体响应 - 写了
SELECT COUNT(*) FROM huge_table WITH (NOLOCK)却仍被阻塞:说明阻塞来自元数据锁(SCH-S),比如别人正在ALTER TABLE
验证当前会话隔离级别:SELECT transaction_isolation_level FROM sys.dm_exec_sessions WHERE session_id = @@SPID。值为 2 是 READ COMMITTED,3 是 REPEATABLE READ,4 是 SERIALIZABLE。
避免在事务里跑长耗时聚合查询
这是最常被忽略的实践错误:把报表类聚合(如日结统计)包进一个显式事务里,还忘了 COMMIT。结果就是——锁一直挂着,所有依赖该表的写操作全被拦住。
确认方式很简单:
- 查
sys.dm_tran_active_transactions,看transaction_begin_time是否异常早 - 查
sys.dm_exec_requests中该会话的open_transaction_count> 0,且status是suspended或running - 它的
sql_handle解析出的语句里有BEGIN TRAN但没明显COMMIT或ROLLBACK
这类问题不会在查询执行完就消失,只要事务开着,锁就一直存在。重启连接、杀会话、或手动 COMMIT 是唯一直接解法。

















