SQL Server查询优化

翻译自:https://mssqlwiki.com/2012/11/06/tuning-sql-server-query/


SQL Server查询优化或者说在SQL Server里调优慢查询。

在SQL Server里如何调优慢查询,优化慢查询让其运行得更快,解决SQL Server错误-2147217871查询超时过期并让它们运行得更快?

当查询时间比期待的要长时被认为是慢查询。一个查询总的持续时间可以分解为编译时间、CPU时间和等待时间。

在你开始排除运行时间慢的查询故障之前,要识别查询慢是因为长时间的等待、还是长时间的运行,或者长时间的编译。

编译时间(Compile time):
编译查询花费的时间。编译时间可以通过查看以下来识别:

  1. 在XML计划里,CompileTime="n"

  2. 当Set statistics time on启用时,SQL Server分析和编译时间。


CPU时间(CPU time):
查询在CPU上花费的时间(Execution time - (compile time + wait time))。CPU时间可以通过查看以下来识别:

  1. 在Profiler里CPU列。

  2. 当Statistics time on启用时,在SQL Server Execution Times下的CPU时间。


执行时间(Execution time):
查询用于完成执行所花费的时间(Execution time = CPU time(CPU time for compilation+exectuion) + Wait time)。一个查询的总持续时间可以通过以下来识别:

  1. 在Profiler里Duration列。

  2. 当Statistics time on启用时,SQL Server Execution Times,elapsed times。


什么是长时间等待的查询?

当一个查询花费大多数时间在等待某个资源上,它被认为是长时间等待的查询。

如果一个查询长时间等待,如何识别?

慢查询可以通过比较Profiler里的CPU和Duration列,或者当statistics time on启用时的CPU和elapsed time来识别。

当一个查询等待一个资源(像lock、network I/O、Page I/O等),他将不会消耗CPU。因此,如果你看到Duration比CPU高(Duration和CPU的不同是Wait time),它表明查询花了大量的时间等待某个资源。

让我们看一个长时间等待的查询的示例。我已经收集了当执行一个查询时的Profiler跟踪。

set statistics io on
set statistics time on
go
--Place your query here
select top 10000 * from a
go
set statistics io off
set statistics time off
go


在这个Profiler里查看Duration和CPU列:Cpu=256 and duration =1920。所以这个查询话费了大量的时间等待某个资源。


在以上图片中,查看statistics time和statistics I/O的输出。

SQL Server只花了2 milliseconds编译查询和256 milliseconds在CPU上,但是整个持续时间是1920 milliseconds,所以,这个查询话费了最大的时间用于等待某个资源。

使用以下步骤中的一个来识别查询正在等待的资源:

  1. 当查询执行时,对于该查询的spid查询sysprocesses的waittype列。

  2. 如果在服务器上没有其他活动,在查询执行前后收集sys.dm_os_wait_stats的输出,并识别这个等待(将有助于调优查询运行得更快)。

  3. 开启XEvent来收集单个查询的wait stats。


一旦你识别出了查询等待的资源,那么调优该资源。大多数时候查询在等待以下资源时会很慢:

PAGEIOLATCH_* 或者 Write log:
这表明有I/O资源瓶颈,参照在这里(https://mssqlwiki.com/2012/08/27/io-requests-taking-longer-than-15-seconds-to-complete-on-file/)提到的故障排除步骤来修复I/O瓶颈。如果你发现SQL Server产生过多的I/O,创建需要的索引。
    a. 参照以上图片中statistics I/O输出的Logical reads + Physical reads,或者在Profiler里的Reads and writes,将会表明这个查询产生的I/O。如果与查询返回的剩下的结果相比,你看到非常高的读,这表明索引缺失或者糟糕的执行计划。创建需要的索引(你可以使用DTA用于索引推荐)。

PAGELATCH_*:
在sysprocesses里的这个waittype表明SQL Server正等待访问一个数据库页,但是该页没有经历物理I/O。
    a. 这个问题通常是由大量的会话尝试在相同的时间访问相同的物理页。我们应该查看这个spid的等待资源,这个正被访问的wait_resource是页号(格式是dbid:file:pageno)。
    b. 我们可以使用DBCC PAGE来识别对象或有争用的页的类型。页可以帮助我们确定是否争用发生在分配、数据还是文本。
    c. 如果SQL Server最频繁等待的页是在Tempdb数据库,在dbid为2的一个页号检查等待资源列,例如(2:1:1或1:1:2)。启用跟踪标志1118并增加TEMPDB数据文件,并平均设置它们的大小(你可能正面临着tempdb分配闩锁冲突http://support.microsoft.com/kb/328551)。
    d. 如果该页在一个用户数据库,检查是否该表在一个单调键像标识列上有一个聚集索引,所有的线程在表尾部争用相同的页。在这里我们需要选择一个不同聚集索引键将工作分散到不同的页。

LATCH_*:
Non-buf latch等待可以被各种事情导致。我们可以使用在sysprocesses的等待资源列来确定包含的闩锁类型(KB 822101)。
    a. 一个非常普通的LATCH_EX是由于运行了一个Profiler跟踪或者sp_trace_getdata,参考KB 929728或得更多信息。
    b. 当查询执行是的自动增长和自动收缩。
    c. 查询过度并行。

Blocking(LCK*):
使用这里的(https://mssqlwiki.com/2010/11/24/script-to-get-current-blocking-tree-with-wait-types/)查询脚本来识别阻塞。调优阻塞源头。

Asynch_network_io 或 network_io:
保持查询返回的结果集更小。更详细的信息,参考这里的(https://mssqlwiki.com/sqlwiki/sql-performance/async_network_io-or-network_io/)故障排除。

Resource_semaphore waits:
确保服务器端没有内存压力,更详细的故障排除,可以参考这里:https://mssqlwiki.com/2012/10/12/what-is-resource_semaphore_query_compile/

SQL Trace:
停止运行在服务器上的所有Profiler跟踪。使用这个查询(https://mssqlwiki.com/2010/04/26/how-to-find-all-the-profiler-traces-running-on-my-sql-server/)识别运行在服务器上的所有跟踪。

Cx packet:
查看最大并行度。但是记住Cxpacket等待类型不总是一个问题。
    a. 对于有8个及以下CPU的服务器,使用如下配置,N等于CPU数量:max degree of parallelism = 0 到 N。
    b. 对于多于8个CPU的服务器,使用如下配置:max degree of parallelism = 8。参考http://support.microsoft.com/kb/2023536

SOS_SCHEDULER_YIELD:
识别是否在服务器上有CPU瓶颈。这个等待意味着线程正在等待CPU。
    a. SQL Server worker thread的额度(Quantum)目标是4ms,意味着这个thread(worker)被期望切换回SQL Server scheduler,当它超过4ms并且在它切换回之前,它检查有任何其他可运行的线程,如果有可运行的线程,那么在可运行列表最上面的线程被调度并且当前的线程将会到可运行列表的尾部,并且当在SOS调度器(可运行列表)里的其他线程完成了它的执行或额度,将被重新调度。在可运行列表等待它的额度的线程花费的时间被算为SOS_SCHEDULER_YIELD。当多线程正在等待获得CPU你可以看到这个类型。故障排除相关的步骤,可以参考https://mssqlwiki.com/2012/10/04/troubleshooting-sql-server-high-cpu-usage/

重要:在SQL Server实例,当有多于1个CPU,可能CPU高于持续时间。因为CPU是当选择并行时所有CPU消耗的时间的总和,而持续时间是查询实际的时间。

什么是长时间运行的查询?

当一个查询花费了大多数时间在CPU上,并且没有等待某个资源,它被认为是一个长时间运行的查询。

如何识别长时间运行的查询?

长时间运行的查询可以通过比较Profiler里的CPU和duration列,或者当statistics time on启用时的CPU和elapsed time来识别。如果CPU和duration相近,那么这个查询被认为是长时间运行的查询。如果查询时长时间运行的,识别查询将时间消耗在哪里,是用在编译(compiling)或者编译后(post compilation)(用于执行查询)。比较查询的duration与CompileTime(XML计划里的compile或者当stattistics time on启用时的SQL Server分析和编译时间)。

High Compile time:
比较查询的duration和Compile Time(XML计划里的compile time,或者当statistics time on启用时的SQL Server分析和编译时间)。编译时间通常为几millisecond。跟着以下步骤查看是否有高编译时间:

  1. 参考http://support.microsoft.com/kb/927396识别是否你有large token perm。

  2. 创建需要的索引和统计信息。手工调优查询或者在DTA应用推荐。

  3. 降低查询的复杂度。多表关联的查询,或者有大量N从句可能要花长时间来完成。

  4. 你可以使用强制参数化选项来减少编译。


High CPU time:
比较查询的duration和Compile Time(XML计划的compile time,或者当statistics time on启用时的SQL Server分析和编译时间)。如果与duration相比编译时间很少。那么遵照以下步骤:

  1. 更新查询使用的表和索引的统计信息(统计信息到目前为止Estimated rows和estimated execution在执行计划里大约相等。如果有大量不同的统计信息过去,那么需要更新)。

  2. 识别是否查询因为参数嗅探(parameter sniffing,如果在XML计划里ParameterCompiledValue和ParameterRuntimeValue不同)使用了糟糕的执行计划。从这里(https://mssqlwiki.com/2012/10/08/parameter-sniffing/)可以了解更多关于参数嗅探的信息。

  3. 如果更新统计信息和修复参数嗅探不能解决以上问题,很有可能优化器由于缺少索引和正确的统计信息而不能创建有效的计划。在数据库调优向导运行查询并应用推荐。(你可以在XML计划找到缺失的索引详情,但是DTA更为有效)。

  4. 如果运行时间更长并消耗CPU的查询时链接服务器查询,那么尝试修改链接服务器的安全性,确保链接服务器用户在远程服务器有ddl_admin或者dba/sysadmin。相关问题的更多详情,请参考:http://blogs.msdn.com/b/psssql/archive/2009/09/02/distributed-queries-remote-login-permissions-and-execution-plans.aspx

  5. 确保优化器没有很早终止并创建糟糕的执行计划。更多详情,请参考:https://mssqlwiki.com/2012/10/07/optimizer-timeout-or-optimizer-memory-abort/

  6. 确保消耗CPU的查询没有计划向导(在XML计划里会有PlanGuideDB属性。sys.plan_guides也会有条目)和查询提示(index=或者(option xxx join)或者inner (Join Hint) join)。

  7. 确保SET选项没有改变。