对 SQL Server 中似乎永不结束的查询进行故障排除
本文介绍查询似乎从未完成的问题的故障排除步骤,或者完成查询可能需要几个小时或几天时间。
什么是永不结束的查询?
本文档重点介绍继续执行或编译的查询,即 CPU 继续增加。 它不适用于被阻止或等待某些从未释放的资源的查询(CPU 保持不变或更改很少)。
重要
如果查询保留以完成其执行,最终将完成。 可能需要几秒钟,或者可能需要几天时间。
术语永不结束用于描述实际上查询最终完成时未完成的查询的感知。
标识永不结束的查询
若要确定查询是持续执行还是停滞在瓶颈上,请执行以下步骤:
运行以下查询:
DECLARE @cntr int = 0 WHILE (@cntr < 3) BEGIN SELECT TOP 10 s.session_id, r.status, r.wait_time, r.wait_type, r.wait_resource, r.cpu_time, r.logical_reads, r.reads, r.writes, r.total_elapsed_time / (1000 * 60) 'Elaps M', SUBSTRING(st.TEXT, (r.statement_start_offset / 2) + 1, ((CASE r.statement_end_offset WHEN -1 THEN DATALENGTH(st.TEXT) ELSE r.statement_end_offset END - r.statement_start_offset) / 2) + 1) AS statement_text, COALESCE(QUOTENAME(DB_NAME(st.dbid)) + N'.' + QUOTENAME(OBJECT_SCHEMA_NAME(st.objectid, st.dbid)) + N'.' + QUOTENAME(OBJECT_NAME(st.objectid, st.dbid)), '') AS command_text, r.command, s.login_name, s.host_name, s.program_name, s.last_request_end_time, s.login_time, r.open_transaction_count, atrn.name as transaction_name, atrn.transaction_id, atrn.transaction_state FROM sys.dm_exec_sessions AS s JOIN sys.dm_exec_requests AS r ON r.session_id = s.session_id CROSS APPLY sys.Dm_exec_sql_text(r.sql_handle) AS st LEFT JOIN (sys.dm_tran_session_transactions AS stran JOIN sys.dm_tran_active_transactions AS atrn ON stran.transaction_id = atrn.transaction_id) ON stran.session_id =s.session_id WHERE r.session_id != @@SPID ORDER BY r.cpu_time DESC SET @cntr = @cntr + 1 WAITFOR DELAY '00:00:05' END
检查示例输出。
当你注意到与以下输出类似的输出时,本文中的故障排除步骤特别适用,其中 CPU 在经过的时间中按比例增加,且没有明显的等待时间。 请务必注意,在这种情况下,更改
logical_reads
并不相关,因为某些 CPU 绑定的 T-SQL 请求可能根本不执行任何逻辑读取(例如执行计算或WHILE
循环)。session_id status cpu_time logical_reads wait_time wait_type 56 “正在运行” 7038 101000 0 Null 56 可运行 12040 301000 0 Null 56 “正在运行” 17020 523000 0 Null 如果观察到的等待方案与以下情况类似,则本文不适用,其中 CPU 不会更改或更改非常轻微,并且会话正在等待资源。
session_id status cpu_time logical_reads wait_time wait_type 56 suspended 0 3 8312 LCK_M_U 56 suspended 0 3 13318 LCK_M_U 56 suspended 0 5 18331 LCK_M_U
有关详细信息,请参阅 诊断等待或瓶颈。
编译时间长
在极少数情况下,你可能会发现 CPU 在一段时间内持续增加,但这不是由查询执行驱动的。 相反,它可能由过多的编译(查询分析和编译)驱动。 在这些情况下,请检查 transaction_name 输出列并查找值 sqlsource_transform
。 此事务名称指示编译。
收集诊断数据
- SQL Server 2008 - SQL Server 2014(SP2 之前)
- SQL Server 2014(SP2 之后)和 SQL Server 2016(SP1 之前)
- SQL Server 2016(SP1 之后)和 SQL Server 2017
- SQL Server 2019 及更高版本
若要使用 SQL Server Management Studio (SSMS)收集诊断数据,请执行以下步骤:
查看收集的计划的方法
本部分将演示如何查看收集的数据。 它将使用 SQL Server 2016 SP1 和更高版本中收集的多个 XML 查询计划(使用扩展 *.sqlplan)。
打开以前保存的查询执行计划文件(.sqlplan)。
右键单击执行计划的空白区域,然后选择“ 比较显示计划”。
选择要比较的第二个查询计划文件。
查找指示大量行在运算符之间流动的粗箭头。 然后选择箭头前后的运算符,并比较两个计划中的实际行数。
比较第二和第三个计划,以查看行的最大流是否在同一运算符中发生。
下面是一个示例:
解决方法
确保为查询中使用的表更新统计信息。
在查询计划中查找缺少的索引建议并应用任何索引。
使用简化查询的目标重写查询:
尝试使用 查询提示 生成更好的计划:
HASH JOIN
或MERGE JOIN
提示FORCE ORDER
提示FORCESEEK
提示RECOMPILE
PLAN N'<xml_plan>'
如果快速查询计划可以强制使用,请使用
使用 查询存储 (QDS) 强制建立良好的已知计划(如果存在此类计划),如果 SQL Server 版本支持查询存储。
诊断等待或瓶颈
如果问题不是长时间运行的 CPU 驱动查询,请在此处提供此部分作为参考。 可以使用它对由于等待时间较长的查询进行故障排除。
若要优化正在等待瓶颈的查询,请确定等待的时间以及瓶颈的位置(等待类型)。 确认等待类型后,请减少等待时间或完全消除等待时间。
若要计算近似等待时间,请从查询运行时间中减去 CPU 时间(工作时间)。 通常,CPU 时间是实际执行时间,查询生存期的剩余部分正在等待。
如何计算近似等待持续时间的示例:
已用时间 (ms) | CPU 时间(毫秒) | 等待时间(ms) |
---|---|---|
3200 | 3000 | 200 |
7080 | 1000 | 6080 |
确定瓶颈或等待
若要标识历史长时间等待查询(例如, >20% 的总运行时间是等待时间),请运行以下查询。 自 SQL Server 启动以来,此查询使用缓存查询计划的性能统计信息。
SELECT t.text, qs.total_elapsed_time / qs.execution_count AS avg_elapsed_time, qs.total_worker_time / qs.execution_count AS avg_cpu_time, (qs.total_elapsed_time - qs.total_worker_time) / qs.execution_count AS avg_wait_time, qs.total_logical_reads / qs.execution_count AS avg_logical_reads, qs.total_logical_writes / qs.execution_count AS avg_writes, qs.total_elapsed_time AS cumulative_elapsed_time FROM sys.dm_exec_query_stats qs CROSS apply sys.Dm_exec_sql_text (sql_handle) t WHERE (qs.total_elapsed_time - qs.total_worker_time) / qs.total_elapsed_time > 0.2 ORDER BY qs.total_elapsed_time / qs.execution_count DESC
若要识别当前执行时间超过 500 毫秒的查询,请运行以下查询:
SELECT r.session_id, r.wait_type, r.wait_time AS wait_time_ms FROM sys.dm_exec_requests r JOIN sys.dm_exec_sessions s ON r.session_id = s.session_id WHERE wait_time > 500 AND is_user_process = 1
如果可以收集查询计划,请从 SSMS 中的执行计划属性检查 WaitStats:
- 运行包含 实际执行 计划的查询。
- 在“执行计划”选项卡中右键单击最左侧的运算符
- 选择“属性”,然后选择 WaitStats 属性。
- 检查 WaitTimeMs 和 WaitType。
如果熟悉 PSSDiag/SQLdiag 或 SQL LogScout LightPerf/GeneralPerf 方案,请考虑使用其中任一方案收集性能统计信息并识别 SQL Server 实例上的等待查询。 可以使用 SQL Nexus 导入收集的数据并分析性能数据。
帮助消除或减少等待的参考
每种等待类型的原因和解决方法各不相同。 没有一种常规方法来解析所有等待类型。 下面是排查和解决常见等待类型问题的文章:
- 了解和解决阻止问题(LCK_M_*)
- 了解并解决 Azure SQL 数据库阻塞问题
- 排查 I/O 问题导致的 SQL Server 性能缓慢问题(PAGEIOLATCH_*、WRITELOG、IO_COMPLETION、BACKUPIO)
- 解决 SQL Server 中的最后一页插入 PAGELATCH_EX 争用
- 内存授予解释和解决方案(RESOURCE_SEMAPHORE)
- 排查ASYNC_NETWORK_IO等待类型导致的慢查询问题
- 使用 AlwaysOn 可用性组排查高HADR_SYNC_COMMIT等待类型问题
- 工作原理:CMEMTHREAD 和调试它们
- 使并行度等待可操作(CXPACKET 和 CXCONSUMER)
- THREADPOOL 等待
有关许多等待类型及其指示的说明,请参阅“等待类型”中的表。