SQL Server休眠会话sql_text/sql_command为NULL的原因与排查问询
SQL Server休眠会话未提交事务且SQL文本为空的问题排查
我在监控本地部署的SQL Server 2017/2019/2022环境时,频繁发现状态为**休眠(sleeping)**但sql_text和sql_command字段为空/NULL的会话,这类会话还存在未提交事务(open_transaction_count>0)。比如session_id 73,关联master库,已空闲42秒,SQL文本字段均为空。使用的监控脚本基于DMV,逻辑类似sp_whoisactive,核心代码如下:
SELECT RIGHT('00' + CAST(DATEDIFF(SECOND, COALESCE(B.start_time, A.login_time), GETDATE()) / 86400 AS VARCHAR), 2) + ' ' + RIGHT('00' + CAST((DATEDIFF(SECOND, COALESCE(B.start_time, A.login_time), GETDATE()) / 3600) % 24 AS VARCHAR), 2) + ':' + RIGHT('00' + CAST((DATEDIFF(SECOND, COALESCE(B.start_time, A.login_time), GETDATE()) / 60) % 60 AS VARCHAR), 2) + ':' + RIGHT('00' + CAST(DATEDIFF(SECOND, COALESCE(B.start_time, A.login_time), GETDATE()) % 60 AS VARCHAR), 2) + '.' + RIGHT('000' + CAST(DATEDIFF(SECOND, COALESCE(B.start_time, A.login_time), GETDATE()) AS VARCHAR), 3) AS Duration, B.command, A.session_id AS session_id, TRY_CAST('<?query --' + CHAR(10) + ( SELECT TOP 1 SUBSTRING(X.[text], B.statement_start_offset / 2 + 1, ((CASE WHEN B.statement_end_offset = -1 THEN (LEN(CONVERT(NVARCHAR(MAX), X.[text])) * 2) ELSE B.statement_end_offset END ) - B.statement_start_offset ) / 2 + 1 ) ) + CHAR(10) + '--?>' AS XML) AS sql_text, TRY_CAST('<?query --' + CHAR(10) + X.[text] + CHAR(10) + '--?>' AS XML) AS sql_command, FORMAT(COALESCE(B.cpu_time, 0), '###,###,###,###,###,###,###,##0') AS CPU, FORMAT(COALESCE(B.granted_query_memory, 0), '###,###,###,###,###,###,###,##0') AS used_memory, 'KILL ' + CAST(A.session_id AS VARCHAR(10)) AS kill_command, (CASE WHEN B.[deadlock_priority] <= -5 THEN 'Low' WHEN B.[deadlock_priority] > -5 AND B.[deadlock_priority] < 5 AND B.[deadlock_priority] < 5 THEN 'Normal' WHEN B.[deadlock_priority] >= 5 THEN 'High' END) + ' (' + CAST(B.[deadlock_priority] AS VARCHAR(3)) + ')' AS [deadlock_priority], A.[status], A.[host_name], COALESCE(DB_NAME(CAST(B.database_id AS VARCHAR)), 'master') AS [database_name], COALESCE(B.start_time, A.last_request_end_time) AS start_time, W.query_plan FROM sys.dm_exec_sessions AS A WITH (NOLOCK) LEFT JOIN sys.dm_exec_requests AS B WITH (NOLOCK) ON A.session_id = B.session_id JOIN sys.dm_exec_connections AS C WITH (NOLOCK) ON A.session_id = C.session_id AND A.endpoint_id = C.endpoint_id LEFT JOIN msdb.dbo.sysjobs AS D ON RIGHT(D.job_id, 10) = RIGHT(SUBSTRING(A.[program_name], 30, 34), 10) LEFT JOIN ( SELECT session_id, wait_type, wait_duration_ms, resource_description, ROW_NUMBER() OVER(PARTITION BY session_id ORDER BY (CASE WHEN wait_type LIKE 'PAGE%LATCH%' THEN 0 ELSE 1 END), wait_duration_ms) AS Ranking FROM sys.dm_os_waiting_tasks ) E ON A.session_id = E.session_id AND E.Ranking = 1 LEFT JOIN ( SELECT session_id, request_id, SUM(internal_objects_alloc_page_count + user_objects_alloc_page_count) AS tempdb_allocations, SUM(internal_objects_dealloc_page_count + user_objects_dealloc_page_count) AS tempdb_current FROM sys.dm_db_task_space_usage GROUP BY session_id, request_id ) F ON B.session_id = F.session_id AND B.request_id = F.request_id LEFT JOIN ( SELECT blocking_session_id, COUNT(*) AS blocked_session_count FROM sys.dm_exec_requests WHERE blocking_session_id != 0 GROUP BY blocking_session_id ) G ON A.session_id = G.blocking_session_id OUTER APPLY sys.dm_exec_sql_text(COALESCE(B.[sql_handle], C.most_recent_sql_handle)) AS X OUTER APPLY sys.dm_exec_query_plan(B.plan_handle) AS W LEFT JOIN sys.dm_resource_governor_workload_groups H ON A.group_id = H.group_id WHERE A.session_id > 50 AND A.session_id <> @@SPID AND (A.[status] != 'sleeping' OR (A.[status] = 'sleeping' AND A.open_transaction_count > 0))
问题1:为何状态为休眠且open_transaction_count>0的会话,其sql_text和sql_command会返回NULL?
核心原因有两点:
- 休眠状态的会话没有活跃请求,
sys.dm_exec_requests(脚本中的B表)无对应记录,导致B.sql_handle为NULL;若连接的most_recent_sql_handle(脚本中的C表字段)已被SQL Server缓存清理机制淘汰,或会话自建立后未执行任何SQL(比如只开启事务未跑查询),sys.dm_exec_sql_text就会返回NULL。 - 未提交事务可能通过隐式事务开启(比如应用设置
SET IMPLICIT_TRANSACTIONS ON),这类场景下会话可能没有执行显式SQL语句,自然不会留下SQL文本缓存。
问题2:如何更高效地排查这类NULL会话的来源?
可以按以下步骤逐层排查:
- 先定位基础来源:查看
sys.dm_exec_sessions的program_name、host_name、login_name字段,直接关联到对应的应用程序、客户端或登录账号;脚本已关联msdb.dbo.sysjobs,可直接查看是否为SQL Agent作业导致。 - 确认连接属性:从
sys.dm_exec_connections获取net_transport(连接方式)、client_net_address(客户端IP),缩小客户端范围。 - 追踪事务事件:启用Extended Events或SQL Server Profiler,追踪
Begin Tran、Commit Tran、Rollback Tran事件并关联会话ID,定位未提交事务的触发点。 - 查看事务详情:通过
sys.dm_tran_session_transactions和sys.dm_tran_active_transactions获取事务类型(显式/隐式)、开始时间,进一步缩小排查范围。
问题3:若sql_handle或most_recent_sql_handle已不在缓存或无效,能否获取导致事务未提交的查询文本或操作信息?
直接获取原查询文本的可能性极低,但可以通过间接方式获取线索:
- 查看事务关联资源:通过
sys.dm_tran_locks查看事务持有的锁对象,关联到具体表、索引,推测可能的操作类型(比如插入、更新)。 - 检查应用日志:应用程序通常会记录事务相关的操作步骤,尤其是异常场景下的未提交情况,可从应用日志中回溯。
- 持久化事件追踪:提前启用Extended Events的
sql_statement_starting、sql_statement_completed事件并持久化存储,后续遇到问题时可回溯历史记录。 - 排查作业日志:若为SQL Agent作业导致,查看作业历史执行日志,检查步骤中是否存在未提交事务的逻辑。
问题4:这种情况通常是应用连接与事务管理问题,还是SQL Server某些场景下的预期行为?
绝大多数情况属于应用连接与事务管理问题,常见场景包括:
- 应用代码的try-catch-finally块遗漏COMMIT/ROLLBACK,比如异常发生时直接抛出错误,未进入finally块执行事务收尾。
- 开启隐式事务(
SET IMPLICIT_TRANSACTIONS ON)后,未显式提交或回滚,会话休眠后事务持续挂起。 - 连接池复用问题:连接被复用后,前一个事务未正确收尾,导致新请求复用了带有未提交事务的连接。
少数场景属于SQL Server预期行为,比如:
- 会话执行
BEGIN TRAN后未执行任何DML/DDL就进入休眠,这类通常是测试或手动操作导致的临时事务挂起;但系统进程(session_id<=50)的类似情况属于正常机制,你的脚本已过滤这类会话。
内容的提问来源于stack exchange,提问作者Jvmnz_17
相关产品推荐
相关产品推荐

