You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 22:53:15