很少会有偶然的性能下降。设计不良的数据库或工作负载配置不正确的系统会经常导致性能问题。管理员需要能预先阻止或最小化问题的影响,当管理员遇到问题时,应该诊断问题并采取正确操作来修复问题。本文提供了按部就班的指导,通过使用可用的工具例如SQL Server Profiler,System Monitor和在SQL Server 2005中新的Dynamic Management View来为一般的性能问题诊断和排错。



[b]目录[/b]

简介

目标

方法

资源瓶颈

解决资源瓶颈的工具

CPU 瓶颈

过多的编译和重编译

检测

解决

效率低的查询计划

检测

解决

内部查询的并行

检测

解决

拙劣游标使用

检测

解决

内存瓶颈

背景

虚拟地址空间和物理内存

Windows地址扩展和SQL Server

内存压力

检测内存压力

外部物理内存压力

外部虚拟内存压力

内部物理内存压力

高速缓存和内存压力

Ring buffers

内部虚拟内存压力

一般的内存错误排错步骤

内存错误

I/O 瓶颈

解决

Tempdb

监视tempdb空间

磁盘空间问题排错

用户对象

版本存储

内部对象

过多的DLL和分配操作

解决

运行缓慢的查询

阻塞

识别长时间的阻塞

通过sys.dm_db_index_operational_stats查看阻塞的每个对象

使用SQL waits阻塞对整体性能的影响

监视索引的使用

总结

附录A: DBCC MEMORYSTATUS 描述

附录B: 阻塞脚本

分析操作的索引统计

等待状态



[b]简介[/b]

很多客户偶尔会遇到SQL Server 数据库性能下降。原因可能涉及从不良好的数据库设计到不正确的负载配置。作为一个管理员,你应该预先阻止或最小化问题,并当问题发生时,诊断原因并尽可能的做出正确的操作来解决问题。这片白皮书所述的问题通常来源于Microsoft® Corporation 的Customer Support Service(CSS or PSS)部门所遇到的,因为将所有可能的问题都详尽的分析是不合实际的。我们提供了按部就班的指导,通过使用可用的工具例如SQL Server Profiler,System Monitor和在SQL Server 2005中新的Dynamic Management View来为一般的性能问题诊断和排错。



[b]目标[/b]

.这篇文章的主要目标是提供一套常规的方法通过使用公开的工具在一般的客户场景中诊断和排错SQL Server性能问题。



SQL Server 2005在用户支持上有了很大的提高。内核层(SQL-OS)被重新架构过,内部结构和统计数据通过动态管理视图(DMVs)暴露为关系型行集。SQL Server 2000通过像sysprocesses这样的系统表暴露一些信息,但是有时你需要将SQL Server进程内存映射为物理文件并从中抽取内部结构的相关信息。这里有2个主要的问题。第一,客户不能总是提供物理映射文件,因为文件的尺寸原因,并且这个过程很耗时。第二,这将花费更长的时间诊断问题,因为文件必须传回Microsoft Corporation来分析。

这带给我们本文的第二个目标,展示DMVs的优点。DMVs通过除去大多数情况下需要的生成和分析物理映射步骤可以加速调试的过程。本文尽可能的提供了和SQL Server 2000中同样问题的比较。DMVs提供为获取关键系统信息的简单而熟悉的界面。这些信息也可以用于监视目的,警告管理员潜在的问题。或者也可以被周期性的收集为以后的分析所用。



[b]方法[/b]

这里有很多降低SQL Server速度的原因。我们使用下列3个主要症状来诊断问题。

• 资源瓶颈: CPU,内存,和I/O瓶颈是在本文中主要涉及的。这里我们不考虑网络因素。对每种资源瓶颈,我们会描述如何识别问题并阐述可能的原因。例如,内存瓶颈可以导致过多的分页,最后影响性能。

• Tempdb 瓶颈:因为每个SQL Server 实例只有一个tempdb,这可以产生性能和磁盘空间的瓶颈。不好的应用程序在过多的DDL和DML操作会使tempdb过载。这导致其他在这台服务器上运行的不相关的应用程序运行缓慢或失败。

• 缓慢运行的用户查询:现有的查询性能下降或新的查询显示比预期时间更长。这可能有很多原因。例如:

• 改变统计信息可以导致现有查询的较差的查询计划。

• 制表扫描,降低查询性能。

• 即使资源利用正常由于阻塞也可以导致应用程序运行缓慢。

过多的阻塞可能是由于不良的应用程序设计或架构设计或者是选择了错误的事务隔离级别的原因。

这些症状的原因不需要每个都独立出来。不良的查询计划选择可以使系统资源加重并导致整体性能的下降。所以,如果大表缺失的有用的索引,或查询优化器没有选择它,这样不仅导致查询缓慢,也会导致将不需要数据页读取到内存(buffer pool)中在缓存中存储,这样会加重I/O子系统的压力。同样的,频繁运行查询的重编译可以导致CPU的压力。



[b]资源瓶颈[/b]

接下来的部分讨论了CPU,内存和I/O子系统资源及这些瓶颈如何产生。(网络问题不在本文的讨论范围)每个资源瓶颈,我们描述了如何识别问题并找到原因。例如,一个内存瓶颈可以导致过渡分页,将会影响性能。

在决定你遇到资源瓶颈前,你需要了解在普通环境下资源是如何使用的。你可以使用在这片白皮书中所列出的方法收集有关资源使用的基线信息(即使你没有遇到性能问题)。

你也许找到问题是一个资源运行到设计容量并且SQL Server当前的配置不能支持这种负载。为了解决这问题,你也许需要添加处理能力,内存或增加I/O的带宽或网络通道。但是,在你进行下一步之前,理解这种资源瓶颈的通常原因是非常重要的。有一些解决方案不需要添加额外资源,例如重新配置。



[b]解决资源瓶颈的工具[/b]

下列工具是在解决特殊性能瓶颈时经常使用的工具。

• 系统监视器(PerfMon):这是Windows所带的一个工具。更多信息,请见系统监视器文档。

• SQL Server Profiler:在SQL Server 2005程序组中的Performance Tools组中可以看到SQL Server Profiler。

• DBCC 命令:详细信息请见SQL Server联机丛书中和附录A。

• DMVs: 详细信息请见SQL Server联机丛书。



[b]CPU 瓶颈[/b]

当没有额外负载,突然发生的CPU瓶颈通常由于没有查询计划,不良的配置或设计因素和不足的硬件资源所引起。在购买更快或更多处理器前,你需要首先确定CPU的最大处理能力带宽并查看是否他们都在使用中。

系统监视器一般是确定CPU资源的最好工具。你应该查看Processor:% Processor Time计数器是否偏高;该计数器值超过80%一般被认为是瓶颈。你也可以使用sys.dm_os_schedulers视图来监视是否正在运行的任务不是0。非0的值预示有任务需要等待时间切片来运行;这个数值高表明一个CPU瓶颈的征兆。你可以用下列查询列出所有的调度器并产看等待运行的任务数量。

select

scheduler_id,

current_tasks_count,

runnable_tasks_count

from

sys.dm_os_schedulers

where

scheduler_id < 255

下列查询给你一个查看当前缓存中最耗费CPU的批或过程高级别视图。查询根据具有相同plan handle(意味着属于同一个批或过程)的语句聚合CPU的调用。如果给出的plan handle对应多个语句,你将不得不继续找到在整个CPU使用中最占用资源的查询。

select top 50

sum(qs.total_worker_time) as total_cpu_time,

sum(qs.execution_count) as total_execution_count,

count(*) as number_of_statements,

qs.plan_handle

from

sys.dm_exec_query_stats qs

group by qs.plan_handle

order by sum(qs.total_worker_time) desc

本节剩下的部分将讨论一些其他的在SQL Server 中CPU敏感的操作,也有有效的检查和解决这些问题的方法。



[b]过多的编译和重编译[/b]

当批或远端过程调用(RPC)被提交到SQL Server,在开始执行前,服务器检查查询计划的有效性和正确性。如果这些检查中的一个失败时,批将被再次编译声称不同的查询计划。这种编译就是所说的重编译。当服务器确认当底层数据改变时有更优化的查询计划时,重编译确认正确性再完成编译。编译是CPU敏感的,因此过多的重编译将导致在系统中的CPU性能问题。



在SQL Server 2000中,当SQL Server重编译一个存储过程,整个存储过程都被重编译,不仅是触发重编译的语句。SQL Server 2005引入了语句级的存储过程的重编译。当SQL Server 2005重编译存储过程,只有导致重编译的语句被编译-而不是整个存储过程。这减少了CPU的负载并减少了对例如COMPILE锁的资源争用。重编译可以有多种原因出发,例如:

• 架构变化

• 统计变化

• 延期编译

• Set属性改变

• 临时表改变

• 存储过程创建时使用了RECOMPLIE查询提示或使用了OPTION (RECOMPILE)。



[b]检测[/b]

你可以使用系统监视器(PerfMon)或SQL Trace(SQL Server Profiler)来检测过多的编译和重编译。



[b]系统监视器(Perfmon)[/b]

SQL Statistics对象提供了监视重编译的计数器和发送到SQL Server实例的请求类型。你必须监视查询编译和重编译的数量以及相关联的接受的批的数量来找出是否这个编译是最耗费CPU资源。理想环境下,SQL Recompilations/sec和Batch Request/sec的比值应该很低,除非用户提交了大量的单独查询。

下列显示了关键的数据计数器。

• SQL Server: SQL Statistics: Batch Requests/sec

• SQL Server: SQL Statistics: SQL Compilations/sec

• SQL Server: SQL Statistics: SQL Recompilations/sec

更多信息请看SQL Server联机丛书中的 “SQL Statistics Object”



[b]SQL 跟踪[/b]

如果PerfMon计数器显出了很高的重编译数量,编译将在SQL Server中占用很多的CPU资源。我们将需要查看Profiler 跟踪并从中找到找到被重编译的存储过程。SQL Server Profiler跟踪给出我们重编译原因的信息。你可以使用下列事件。



SP:Recompile和SQL:StmtRecompile事件类指出了哪个存储过程和语句被重编译。当你编译一个存储过程,一个事件为这个存储过程生成,其中每条语句将被编译。然而,当存储过程重编译时,只有导致重编译的语句被重编译(在SQL Server 2000中将是整个存储过程)。下面列出了SP:Recompile事件类更多重要的数据列。特别是EventSubClass数据列决定重编译的原因。SP:Recompile当存储过程或触发器被重编译被触发一次,但不会被独立查询引发。在SQL Server 2005中,监视SQL:StmtRecompiles也非常有用,该事件类在所有类型的重编译中都会被触发,包括批,独立查询,存储过程和触发器。如下是我们关系的事件中关键的数据列:

• EventClass

• EventSubClass

• ObjectID (represents stored procedure that contains this statement)

• SPID

• StartTime

• SqlHandle

• TextData

更多信息,请见SQL Server 联机丛书中“SQL:StmtRecompile Event Class”。



如果你有保存的跟踪文件,你可以使用下列查询查看所有捕捉的重编译事件。

select

spid,

StartTime,

Textdata,

EventSubclass,

ObjectID,

DatabaseID,

SQLHandle

from

fn_trace_gettable ( 'e:\recompiletrace.trc' , 1)

where

EventClass in(37,75,166)

事件类37是 Sp:Recompile, 75 是 CursorRecompile, 166是SQL:StmtRecompile

你可以通过SqlHandle和ObjectID列或其他列将这个查询的结果分组,也可以查看是否最多的重编译类型是存储过程或其他原因(例如SET选项改变等)。



[b]Showplan XML For Query Compile. [/b]

Showplan XML For Query Compile事件发生于Microsoft SQL Server编译或重编译一段SQL语句时。该事件有关于语句编译或重编译的信息。信息包括查询计划和过程的对象ID。捕获这些事件是有性能开销的,因为它捕获了每次编译或重编译。如果你在系统监视其中看到很高的SQL Compilations/sec计数器值,你应该监视这个事件。通过这些信息,你可以看到那条语句被频繁的重编译。你可以使用这些信息改变这些语句的参数。这将影响重编译的数量。



[b]DMVs.[/b]

当你使用sys.dm_exec_query_optimizer_info DMV,你可以得到SQL Server花费在优化的时间。如果获取了这个DMV的2个快照,你可以得到在给定的时间段内花费在查询优化的时间。

select *

from sys.dm_exec_query_optimizer_info



counter occurrence value

---------------- -------------------- ---------------------

optimizations 81 1.0

elapsed time 81 6.4547820702944486E-2



特别是查看elapsed time,该时间由于优化而产生。因为优化过程的时间基本上就是用户优化操作的CPU时间(因为优化处理是CPU时间的主要部分),你可以得到一个好的度量,找到那段编译时间占用了大量的CPU时间。

其他包含有用信息的DMV有:

sys.dm_exec_query_stats.

你希望查看的数据列有 :

• Sql_handle

• Total worker time

• Plan generation number

• Statement Start Offset

更多信息请查看SQL Server 联机丛书相关主题



[b]sys.dm_exec_query_stats.[/b]

特别是plan_generation_num预示了查询编译时的次数。下面是示例给你展示了被重编译次数最多的25个存储过程。

select *

from sys.dm_exec_query_optimizer_info



select top 25

sql_text.text,

sql_handle,

plan_generation_num,

execution_count,

dbid,

objectid

from

sys.dm_exec_query_stats a

cross apply sys.dm_exec_sql_text(sql_handle) as sql_text

where

plan_generation_num >1

order by plan_generation_num desc



[b]解决[/b]

如果你监测到过多的编译/重编译,考虑下列选项。

• 如果重编译因为SET选项改变而发生,使用SQL Server Profiler确定哪个SET选项被改编。避免在存储过程中改变SET选项。如果改变最好在连接级别设置。确认在该连接的生存周期内不要改变SET选项。

在临时表上重编译的阀值比在普通表上的低。在临时表上的重编译时由于统计改变而引起,你可以降临时表改为使用表变量。表变量的改变不会引起重编译。这种方法的确定是查询优化器不识别表变量,因为统计不会被创建或维护表变量。这将导致没有查询计划。你可以测试不同的选项,并选择最好的方法。另外一个选项时使用KEEP PLAN查询提示。设置临时表的这个阀值与使用永久表相同。EventSubclass 列预示了在临时表上的”Statistics Changed”的操作。

• 为避免由于改变统计而产生的重编译(例如,因为数据统计导致计划不理想),特别是KEEPFIXED PLAN查询提示。根据设置的影响,重编译可以仅因为相关正确的原因(例如,当底层表结构改变导致计划不再适用),而不根据统计的变化。如果语句引用的表的架构改变时或者表是被标记为sp_recompile的存储过程,重编译将发生。

• 关闭自动更新索引统计和表或视图的统计,防止由于对象的统计改变而产生的重编译。注意,通过使用这种方法关闭”auto-stats”特性不是一个好的想法。这是因为查询优化器不再为在这些对象上的数据改变而敏感,将导致不良的查询计划。使用这种方法仅在尝试了所有其他选择之后,做为最后的手段。

• 批应该尽量使用对象全名(例如,dbo.Table1)避免重编译并避免不明确的对象。

• 为避免由于延期编译导致的重编译,不要混杂DML和DDL或从条件结构创建DDL,例如IF语句。

• 运行Database Engine Tuning Advisor(DTA)查看改变索引是否可以改善编译时间和查询的执行时间。

• 检查是否存储过程通过WITH RECOMPILE选项创建或使用了RECOMPILE查询提示。如果过程通过WITH RECOMPILE选项创建,在SQL Server 2005中,如果在过程中特殊的语句需要被重编译,我们可以利用语句级的RECOMPILE提示。这将避免在每次执行的时候对整个过程重编译,而同时允许个别语句被编译。更多有关RECOMPILE提示的信息,请查看SQL Server联机丛书。



[b]效率低的查询计划[/b]

当为一个查询生成查询计划时,SQL Server查询优化器尝试选择一个计划为查询提供最快的响应时间。注意最快的查询时间并不意味最小的I/O开销,也不意味使用最少的CPU资源-它会在各种资源中平衡。



某些操作类型比其他操作对CPU更敏感。Hash操作和Sort操作扫描他们各自的输入数据。使用扫描向前读取(prefetch)时,在需要操作页面前,页面几乎都在缓存中。因此可以减少或消除物理I/O操作。这使这些操作的类型将不被物理I/O所限制。与之相比,嵌套循环连接有很多索引查找,如果索引查找使用很多不同的表以至于页面不适合缓存的大小,将导致生成I/O负载。



最有意义的输入优化用于评估为每中操作生成不同查询计划开销的评估,你可以在Showplan(EstimateRows和EstimateExecution属性)中看到结果。没有精确的评估,用于优化的主输入是有缺陷的。



[b]检测[/b]

低效率的查询计划通常可以被检测出来。低效率的查询计划可以导致增加CPU的消耗。

查询sys.dm_exec_query_stats是确定哪个查询累计使用CPU时间最多的有效方法。

select

highest_cpu_queries.plan_handle,

highest_cpu_queries.total_worker_time,

q.dbid,

q.objectid,

q.number,

q.encrypted,

q.[text]

from

(select top 50

qs.plan_handle,

qs.total_worker_time

from

sys.dm_exec_query_stats qs

order by qs.total_worker_time desc) as highest_cpu_queries

cross apply sys.dm_exec_sql_text(plan_handle) as q

order by highest_cpu_queries.total_worker_time desc

还可以选择,也可以查询sys.dm_exec_cached_plans并通过使用过滤器查找可疑的类似于‘%Hash Match%’, ‘%Sort%’这样CPU敏感的各种操作。



[b]解决[/b]

如果你监测到效率低的查询计划,考虑下列选项。

• 使用Database Engine Tuning Advisor调节查询,查看是否生成对修改索引的建议

• 检查有问题的评估。

编写的查询中使用的更有限制性的WHERE从句是否合适?无限制条件的查询是资源敏感的。

在查询中涉及的表上运行UPDATE STATISTICS,检查是否还有这种问题。

是否查询使用的构造导致优化器不能精确的评估?

考虑是否可以将查询修改为其他的方法,避免这种问题。

• 如果不能修改架构或查询,SQL Server 2005有一个新的查询计划特性,允许你将指定查询提示添加到满足某种文本的查询中。这可以用在独立查询中,也可以用在存储过程内。例如OPTION(OPTIMIZE FOR)这样的提示允许你影响评估而忘记所有列出的潜在计划。其他的提示,类似OPTION(FORCE ORDER)或 OPITON(USE PLAN)允许你改变控制查询计划的程度。



[b]内部查询的并行[/b]

当为一个查询生成执行计划时,SQL Server优化器尝试为该查询选择最快的相应计划。如果查询的开销超过了在cost threshold for parallelism选项中指定的值,并行不会被禁用,优化器尝试生成一个可以用于并行的计划。并行查询计划使用多线程处理查询,每个线程分布在可用的CPU上并同时利用每个CPU的时间资源。最大的并行度可以通过服务器上的max degree of parallelism选项或每个查询使用OPTION(MAXDOP)提示限制。



用于执行实际并行度(DOP)的结果——度量有多少线程将在给定的操作上并行——是知道执行时才能确定。在执行查询前,SQL Server 2005决定有多少个调度器未充分利用并为查询选择DOP来充分利用剩余的调度器。一旦一个DOP被选择了,直到完成,查询将使用这个选择的并行度来运行。并行查询的使用时CPU有一些偏高,但是它在elapsed time上的时间很短。如果没有其他瓶颈,类似于物理I/O等待,并行计划将会使用所有处理器的100%资源。



查询开始执行后,一个关键的因素(系统有多空闲)可以导致运行并行计划的改变。例如,如果查询运行在空闲时间,服务器可以选择使用并行计划并使用DOP为4,在4个不同的处理器上产生线程。一旦这种线程开始执行,现存的连接可以提交其他需要大量CPU的查询。在这种情况,所有不同的线程将共享可用的CPU的时间切片,导致更高的查询持续时间。



通过并行计划运行不是一定是不好的,并行可以为查询提供最快的响应时间。然而,给定查询的响应时间必须与整体的吞吐量和系统其他查询的响应进行衡量。并行查询一般最适合批处理和决策支持系统,而不适合一个事务处理环境。



[b]检测[/b]

内部查询的并行问题可以通过下列方法检测。



[b]系统监视器(Perfmon)[/b]

考虑SQL Server:SQL Statistics – Batch Requests/sec 计数器,并查看SQL Server联机丛书中的“SQL Statistics Object”获取更多信息。



因为在考虑使用并行计划前,查询必须评估开销超过为并行配置设置的开销阀值(默认被设置为5),服务器每秒处理的批小于运行在并行计划中的批。运行很多并行查询的服务器一般配置为较小的每秒批请求数(例如,小于100的值)。



[b]DMVs[/b]

在运行的服务器上,你可以使用下列查询确认在给定会话中是否可以并行运行任何活动的请求。

select

r.session_id,

r.request_id,

max(isnull(exec_context_id, 0)) as number_of_workers,

r.sql_handle,

r.statement_start_offset,

r.statement_end_offset,

r.plan_handle

from

sys.dm_exec_requests r

join sys.dm_os_tasks t on r.session_id = t.session_id

join sys.dm_exec_sessions s on r.session_id = s.session_id

where

s.is_user_process = 0x1

group by

r.session_id, r.request_id,

r.sql_handle, r.plan_handle,

r.statement_start_offset, r.statement_end_offset

having max(isnull(exec_context_id, 0)) > 0

通过这些信息,查询的文本可以通过使用sys.dm_exec_sql_text轻松获取,而查询计划可以使用sys.dm_exec_cached_plan获取。



你也可以搜索符合运行在并行的计划。这可以通过搜索缓存的计划来查看如果关系操作符有Parrallel属性为非零的值。这些计划也许可以不运行在平行中,但是他们如果系统不忙,他们也适合这样做。

--

-- Find query plans that may run in parallel

--

select

p.*,

q.*,

cp.plan_handle

from

sys.dm_exec_cached_plans cp

cross apply sys.dm_exec_query_plan(cp.plan_handle) p

cross apply sys.dm_exec_sql_text(cp.plan_handle) as q

where

cp.cacheobjtype = 'Compiled Plan' and

p.query_plan.value('declare namespace p="http://schemas.microsoft.com/sqlserver/2004/07/showplan";

max(//p:RelOp/@Parallel)', 'float') > 0



一般来说,查询的持续时间长于CPU时间总量,因为一些时间花费在等待资源上例如锁或物理I/O。查询使用CPU时间长于持续时间的唯一场景是当查询运行在并行计划例如多线程并发使用CPU。注意并不是所有并行查询将证明这种行为(CPU时间大于持续时间)。

select

qs.sql_handle,

qs.statement_start_offset,

qs.statement_end_offset,

q.dbid,

q.objectid,

q.number,

q.encrypted,

q.text

from

sys.dm_exec_query_stats qs

cross apply sys.dm_exec_sql_text(qs.plan_handle) as q

where

qs.total_worker_time > qs.total_elapsed_time

SQL Trace

Look for the following signs of parallel queries, which could be either statements or batches that have CPU time greater than the duration.



select

EventClass,

TextData

from

::fn_trace_gettable('c:\temp\high_cpu_trace.trc', default)

where

EventClass in (10, 12) -- RPC:Completed, SQL:BatchCompleted

and CPU > Duration/1000 -- CPU is in milliseconds, Duration in microseconds

Or can be Showplans (un-encoded) that have Parallelism operators in them

select

EventClass,

TextData

from

::fn_trace_gettable('c:\temp\high_cpu_trace.trc', default)

where

TextData LIKE '%Parallelism%'



[b]解决[/b]

任何运行在并行计划的查询被查询优化器认为是成本昂贵的,并会超过并行阀值,默认为5(粗略的是在涉及的机器上5秒执行一次)。任何通过上述方法确认的查询都是以后要调节的候选者。

• 使用Database Engine Tuning Advisor查看是否任何索引改变,改变索引视图或分区改变能减少查询的开销

• 检查实际值和评估集的重要不同因为评估集在评估查询开销中是重要因素。如果找到重要的不同:

如果auto create statistics数据库设置被禁用,确认在Showplan输出的Warnings列中没有MISSING STATS项。

尝试在关闭评估的表上运行UPDATE STATISTICS。

验证查询没有使用优化器无法精确评估的查询构造,例如多语句表值函数或CLR函数,表值或Transact-SQL变量比较(参数比较是可以的)。

• 评估是否可以使用不同的Transact-SQL语句或表达式将查询写的更有效率



[b]拙劣游标使用[/b]

SQL Server 2005之前的SQL Server 版本仅支持在每个连接上有单个活动的操作。一个查询正在执行或有了结果等待发送到客户端时将被认为是活动的。在一些情形中,客户端应用程序也许需要从结果中读取并向SQL Server提交其他基于刚刚从结果集中读取的行的查询。这在默认的结果集中是不能实现的,因为还有其他等待的结果。一般的解决方法是改变连接属性是用服务器端游标。



当使用服务器端游标,数据库客户端软件(OLE DB提供者或ODBC驱动)显然会封装客户端请求在特殊的扩展存储过程中,例如sp_cursoropen,sp_cursorfetch等等。这提到了API游标(而不是TSQL游标)。当用户执行查询,查询文本通过sp_cursoropen被发送到服务器,请求读取从sp_cursorfetch指示服务器进发送某些数量的行。通过控制获取行的数量,可以为ODBC驱动或OLE DB提供者缓存行。这阻止发生服务器等待客户端都区所有发送的行的情形。因此,服务器可以在这个连接上接受新的请求。



一次性打开游标并获取1行(或少量行)的应用程序能被网络延时的网络瓶颈影响,特别是在广域网(WAN)。在有快速网络并有不同用户连接时,处理很多游标请求的开销变得更重要。因为开销来自于游标位置的变化来适应在结果集上的位置改变,预请求的处理开销,类似的处理,服务器处理1个请求返回100行必处理100不同请求相同的100行但是每次1行更有效率。



[b]检测[/b]

你可以使用下列方法为拙劣游标使用排错。

系统监视器(Perfmon)

通过考虑SQL Server:Cursor Manager By Type – Cursor Requests/Sec计数器,你可以通过这个性能计数器知道有多少游标在系统中使用。系统还有很高的CPU利用率,因为小量的读取通常会有每秒数百个游标请求。这里没有特殊的计数器告诉你关于获取的缓存大小。



[b]DMVs[/b]

接下来的查询可以用于测定使用API游标(不是TSQL游标)连接获取一行使用的缓存大小。它对于大的获取缓存更有效,例如100行。

select

cur.*

from

sys.dm_exec_connections con

cross apply sys.dm_exec_cursors(con.session_id) as cur

where

cur.fetch_buffer_size = 1

and cur.properties LIKE 'API%' -- API cursor (TSQL cursors always have fetch buffer of 1)



[b]SQL 跟踪[/b]

使用包括RPC:Completed事件类的跟踪用于搜索sp_cursorfetch语句。第4个参数的值是通过获取返回的行数。请求返回的最大行数是被指定为与RPC:Starting事件类关联的参数。

解决

• 确定游标是完成操作的最佳方法或是否基于集合这种更有效的操作是可行的。

• 当连接到SQL Server 2005,考虑使用多活动结果集(MARS)

• 参考你使用的API文档决定如何指定游标的获取缓存大小:

ODBC - SQL_ATTR_ROW_ARRAY_SIZE

OLE DB – IRowset::GetNextRows or IRowsetLocate::GetRowsAt

内存瓶颈

这部分给出了低缓存的条件和对不同内存错误诊断方法,可能的原因和排错方法。



[b]背景[/b]

引用不同的内存资源通过使用简单的术语内存。但是却有一些内存资源类型,对于理解和区分特殊的内存资源这是很重要的。



[b]虚拟地址空间和物理内存[/b]

在Microsoft Windows®,每个进程都有自己的虚拟地址空间(VAS)。进程可用所有虚拟地址有VAS的大小决定。VAS的大小依赖于架构(32位或64位)和操作系统。在排错的上下文中,理解虚拟地址空间使用内存资源,了解应用程序可以超出VAS即使在64位平台只要物理地址可能一直可用,这些很重要。



[b]Windows地址扩展和SQL Server[/b]

Windows地址扩展(AWE)是允许32位应用程序跨越32位地址限制操作内存的API。AWE机制技术上不需要64位平台。然而它出现了。内存页通过AWE机制涉及在64位平台上的锁定页。



在32位和64位平台上,内存通过AWE机制分配不能分页出界。这可以有益于应用程序(这是在64位平台上使用AWE机制的原因)。这也影响了系统和其他应用程序可用RAM总数,这可能是有害的影响。因为这个原因为了使用AWE,Lock Pages in Memory权利必须分配该运行SQL Server的账号。



从排错的角度来看,要点是SQL Server缓存池使用AWE映射内存;然而,只有数据库(hash过的)页面可以利用AWE分配内存。通过AWE机制内存分配将不会在任务管理器或在Process: Private Bytes性能计数器中看到。你需要使用SQL Server特殊的计数器或动态管理视图来获取这些信息。



SQL Server 2005怎样进行性能排错



[b]内存压力[/b]

内存压力表示当可用内存数量受到限制。识别SQL Server何时运行在内存压力下将帮助你排除内存相关的问题。SQL Server依赖于不同类型的内存压力特征也不一样。下表汇总了内存压力类型,和他们潜在的原因。在所有的情况下,你可以更多的会见到超时或显示的内存不足错误消息。



SQL Server 2005怎样进行性能排错



Windows有通知的机制 如果物理内存运行在过高或过低的情况下。SQL Server在他的内存管理决策中使用这种机制。



一般排错的步骤显示在表3中。



SQL Server 2005怎样进行性能排错



[b]工具[/b]

下列工具和资源可以用于排错。

• 内存相关的DMVs

• DBCC MEMORYSTATUS 命令

• 性能计数器: 性能监视器或SQL Server指定对象的DMV

• 任务管理器

• 事件查看器: 应用程序日志和系统日志



[b]检测内存压力[/b]

内存压力自身不会预示问题。内存压力是需要的,但时不是为服务器以后遇到内存错误的充分条件。在内存压力下工作将被任务是服务器的正常操作。然而内存压力的征兆可以于是服务器运行已经接近设计容量并且潜在存在内存不足的错误。在正常运行情况下,这些信息将作为基线决定以后内存不足的原因。



[b]外部物理内存压力[/b]

打开人物管理器的性能视图,检查Physical Memory节的Available项的值。如果可用内存总数很低,这表现了有外部内存压力。这个准确值依赖于很多因素,然而你可以在当这个值降低到50-100MB开始查找问题。当这个总数小于10MB时,外部内存压力将表现得很明显。



相同信息也可以使用在系统监视器中的Memory: Available Bytes计数器获取。



如果存在外部内存压力并且你看到了内存相关的错误,你需要确认在系统中主要的内存消耗者。为了这个,考虑Process: Working Set性能计数器或在任务管理器中Process栏中的Mem Usage列,找到最大的内存消耗者。

系统中所有使用的物理内存可以通过汇总下列计数器获取。

• Process 对象,每个进程的 Working Set计数器

• Memory 对象

• 系统的Cache Bytes计数器

• 未分页池的Pool Nonpaged Bytes 计数器

• Available Bytes (等于任务管理其中的Available 值)

如果没有外部压力,Process: Private Bytes计数器或在任务管理器中虚拟内存将接近工作集的大小(Process: Working Set或任务管理器中的Mem Usage),意味着我们没有内存用于分页了。



注意任务管理器中的Mem Usage列和相应的性能计数器不能计算通过AWE分配的内存。这样如果使用AWE,信息将是不完整的。这种情况下,你需要考虑在SQL Server内分配的内存来获取完整的信息。



你可以使用sys.dm_os_memory_clerks DMV找到SQL Server通过AWE机制分配了多少内存,如下所示。



select

sum(awe_allocated_kb) / 1024 as [AWE allocated, Mb]

from

sys.dm_os_memory_clerks



注意在SQL Server中,只有当前缓存池(’MEMORYCLERK_SQLBUFFERPOOL’类型)使用这种机制并且只能是在使用AWE功能时。通过识别和除去主要物理内存使用者(如果有可能)和/或 通过添加更多的内存的方法解除外部内存压力一般可以解决与此相关的内存问题。



[b]外部虚拟内存压力[/b]

你需要确定是否页面文件为当前内存的分配能提供足够的空间。为了检查,可以打开任务管理器中的性能视图,并检查Commit Charge节。如果Total接近于Limit则说明可以被提交的最大数量内存没有扩展页面的空间。注意任务管理器中的Commit Charge Total指出潜在使用的页面文件,而不是实际使用值。实际使用的页面文件将增加物理内存压力。



同样可以通过下列技术起获取相关信息:Memory: Commit Limit, Paging File: %Usage, Paging File: %Usage Peak。



你可以通过每个进程的Process: Working Set减去Process Private Bytes计数器来评估内存总数。



如果Paging File: %Usage Peak(或Peak Commit Charge)很高,检查系统日志中是否有指出页面文件增长或通知“running low on virtual memory”的信息。你可能需要增加你的页面文件大小。High Paging File: %Usage指出物理内存超过要提交的值并也要考虑外部物理内存压力(大量的内存需求,没有足够的RAM)。



[b]内部物理内存压力[/b]

内部内存压力来自于SQL Server自身,应首先通过检查在缓存分布中的异常来考虑在SQL Server内存分布。通常在SQL Server中缓存会占用最多提交的内存。为了确定在缓存池中的内存总数,我们可以使用DBCC MEMROYSTATUS命令。在Buffer Counts节,可以找到Target值。下列输出显示了在服务器达到正常负载时DBCC MEMORYSTATUS的结果。



Buffer Counts Buffers

------------------------------ --------------------

Committed 201120

Target 201120

Hashed 166517

Reserved Potential 143388

Stolen Potential 173556

External Reservation 0

Min Free 256

Visible 201120

Available Paging File 460640



Target是SQL Server计算出它在不导致分页时可以提交的8-KB每页的页数。Target是被定期的重新计算的来反映内存的低或高。在常规服务负载下target页面过低可能预示出现了外部内存压力。



如果SQL Server占用了大量的内存(通过Process: Private Bytes或 任务管理器中Mem Usage 列显示),请查看是否Target的数值。注意,如果启用AWE,你还要从sys.dm_of_memory_clerks或DBCC MEMORYSTATUS输出计算AWE分配的总量。



考虑上面的示例(没有启用AWE),Target*8KB=1.53GB,而服务器的Process: Private Bytes大约是1.62GB或缓存池用SQL Server占用了94%的内存。注意,如果服务器没有过载,Target是应该超过Process: Private Bytes性能计数器报告的数量。



如果Target过低,但是服务器的Process: Private Bytes或 任务管理器中Mem Usage 值很高,我们也许要面对从缓存池外使用内存的组件带来的内部内存压力。被加载到SQL Server进程中的组件,例如COM对象,连接服务器,扩展存储过程,SQLCLR或其他会从缓存池外占用内存。如果不使用SQL Server内存接口,将没有方法跟踪组件在缓存池外占用的内存。



适用于SQL Server内存管理机制的组件使用在缓存池中分配很少的内存。如果分配的大于8KB,这些组将将通过多页分配器借口使用缓存池外的内存。



下列方法可以快速检查通过多业分配器接口占用的内存数量。



-- amount of mem allocated though multipage allocator interface

select sum(multi_pages_kb) from sys.dm_os_memory_clerks



你可以这样获得通过多页分配器分发内存的详细信息:



select

type, sum(multi_pages_kb)

from

sys.dm_os_memory_clerks

where

multi_pages_kb != 0

group by type

type



------------------------------------------ ---------

MEMORYCLERK_SQLSTORENG 56

MEMORYCLERK_SQLOPTIMIZER 48

MEMORYCLERK_SQLGENERAL 2176

MEMORYCLERK_SQLBUFFERPOOL 536

MEMORYCLERK_SOSNODE 16288

CACHESTORE_STACKFRAMES 16

MEMORYCLERK_SQLSERVICEBROKER 192

MEMORYCLERK_SNI 32



如果通过多页分配器分发了过大的内存( 100-200MB或更多),应该做进一步的研究。



如果你看到了通过多页分配器 分发的大量内存,检查服务器的配置并尝试使用之前或后续的查询确定哪个组件占用的最多的内存。



如果Target值低,但是在百分比上它占用了最多的内存,请在前面部分中查找描述外部内存压力的部分(External Physical Memory Pressure),或查看服务器内存配置参数。



如果你设置了max server memory 和/或min server memory,你应该用这些值和Target值进行比较。max server memory选项限制了在缓存池中占用内存的最大值,而服务器还可以占用其他的部分。min server memory选项告诉服务器当小于该值时不能释放缓存池的内存。如果Target小于min server memory设置并且服务器没有过载,这可能预示服务器遇到了外部内存压力并且不能获得这个设置大小的内存。它也可能预示着从内部组件的内存压力,就像上面描述的那样。Target 数值不能超过max server memory选项的设置。



首先,检查从DBCC MEMORYSTATUS输出中Stolen页面数量



Buffer Distribution Buffers

------------------------------ -----------

Stolen 32871

Free 17845

Cached 1513

Database (clean) 148864

Database (dirty) 259

I/O 0

Latched 0



相对于Stolen和Target页面的高百分比(>75-80%)预示着内部内存压力。

更多关于服务器组件内存分配的信息可以使用sys.dm_of_memory_clerks DMV获取。



-- amount of memory consumed by components outside the Buffer pool

-- note that we exclude single_pages_kb as they come from BPool

-- BPool is accounted for by the next query

select

sum(multi_pages_kb

+ virtual_memory_committed_kb

+ shared_memory_committed_kb) as [Overall used w/o BPool, Kb]

from

sys.dm_os_memory_clerks

where

type <> 'MEMORYCLERK_SQLBUFFERPOOL'



-- amount of memory consumed by BPool

-- note that currenlty only BPool uses AWE

select

sum(multi_pages_kb

+ virtual_memory_committed_kb

+ shared_memory_committed_kb

+ awe_allocated_kb) as [Used by BPool with AWE, Kb]

from

sys.dm_os_memory_clerks

where

type = 'MEMORYCLERK_SQLBUFFERPOOL'



每个组件的详细信息可以通过下列语句获取(这包括从缓存池内和缓存池外分配的内存)。



declare @total_alloc bigint

declare @tab table (

type nvarchar(128) collate database_default

,allocated bigint

,virtual_res bigint

,virtual_com bigint

,awe bigint

,shared_res bigint

,shared_com bigint

,topFive nvarchar(128)

,grand_total bigint

);



-- note that this total excludes buffer pool committed memory as it represents the largest consumer which is normal

select

@total_alloc =

sum(single_pages_kb

+ multi_pages_kb

+ (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL'

THEN virtual_memory_committed_kb

ELSE 0 END)

+ shared_memory_committed_kb)

from

sys.dm_os_memory_clerks



print

'Total allocated (including from Buffer Pool): '

+ CAST(@total_alloc as varchar(10)) + ' Kb'



insert into @tab

select

type

,sum(single_pages_kb + multi_pages_kb) as allocated

,sum(virtual_memory_reserved_kb) as vertual_res

,sum(virtual_memory_committed_kb) as virtual_com

,sum(awe_allocated_kb) as awe

,sum(shared_memory_reserved_kb) as shared_res

,sum(shared_memory_committed_kb) as shared_com

,case when (

(sum(single_pages_kb

+ multi_pages_kb

+ (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL'

THEN virtual_memory_committed_kb

ELSE 0 END)

+ shared_memory_committed_kb))/(@total_alloc + 0.0)) >= 0.05

then type

else 'Other'

end as topFive

,(sum(single_pages_kb

+ multi_pages_kb

+ (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL'

THEN virtual_memory_committed_kb

ELSE 0 END)

+ shared_memory_committed_kb)) as grand_total

from

sys.dm_os_memory_clerks

group by type

order by (sum(single_pages_kb + multi_pages_kb + (CASE WHEN type <> 'MEMORYCLERK_SQLBUFFERPOOL' THEN virtual_memory_committed_kb ELSE 0 END) + shared_memory_committed_kb)) desc



select * from @tab



注意之前的查询将Buffer Pool与通过单页分配器提供给组件的内存视为不同的部分。通过下列查询可以确定在缓存池中耗费内存最多的10个组件(通过单页分配器)。



-- top 10 consumers of memory from BPool

select

top 10 type,

sum(single_pages_kb) as [SPA Mem, Kb]

from

sys.dm_os_memory_clerks

group by type

order by sum(single_pages_kb) desc



你通常不会控制内部组件对内存的占用。然而,确定哪个组件占用最多的内存可以帮助对问题的后续研究。

系统监视器(Perfmon)

你也可以通过检查下列计数器来确定内存压力(SQL Server联机丛书中有详细地描述):

SQL Server: Buffer Manager vL6387D价格对象

• Low Buffer cache hit ratio

• Low Page life expectancy

• High number of Checkpoint pages/sec

• High number Lazy writes/sec

不足的内存和I/O开销经常会导致瓶颈。请查看本文的I/O 瓶颈部分。



[b]高速缓存和内存压力[/b]

查看外部和内部内存压力的方法是查看内存中高速缓存的行为。



SQL Server 2005与SQL Server 2000ISO122U价格的高速缓存设计上有一些细微的不同,其中之一就是统一了高速缓存的框架。为了从高速缓存中删除最近很少使用的项,该框架实现了一套时钟算法。现在它使用2支不同的时钟指针,一个是内部时钟指针,一个是外部时钟指针。



内部时钟指针控制与其他高速缓存相关的缓存大小。当框架预测到高速缓存要使用到尽头是它开始移动。

当SQL Server总体上陷入内存压力时,外部时钟指针开始移动。外部时钟指针的移动可以导致外部和内部的内存压力。在内部和外部内存压力时不会混乱的移动外部时钟和内部时钟。



关于时钟移动的信息可以通过sys.dm_os_memory_cache_clock_hands DMV显示,如下代码所示。每个高速缓存项在内部和外部时钟指针都有不同的行。如果你看到rounds count和removed all rounds count增加,说明服务器遇到内部/外部内存压力。

select *

from

sys.dm_os_memory_cache_clock_hands

where

rounds_count > 0

and removed_all_rounds_count > 0



通过如下所示,通过通过sys.dm_os_cache_counters DMV你可以获得更多关于缓存的信息。

select

distinct cc.cache_address,

cc.name,

cc.type,

cc.single_pages_kb + cc.multi_pages_kb as total_kb,

cc.single_pages_in_use_kb + cc.multi_pages_in_use_kb as total_in_use_kb,

cc.entries_count,

cc.entries_in_use_count,

ch.removed_all_rounds_count,

ch.removed_last_round_count

from

sys.dm_os_memory_cache_counters cc

join sys.dm_os_memory_cache_clock_hands ch on (cc.cache_address = ch.cache_address)

/*

--uncomment this block to have the information only for moving hands caches

where

ch.rounds_count > 0

and ch.removed_all_rounds_count > 0

*/

order by total_kb desc



注意USERSTORE项,正在使用的页面数量将不会被报告,因为结果将是NULL。



[b]Ring buffers[/b]

更多有意义的内存调试信息可以通过sys.dm_os_ring_buffers的ring buffers DMV获取。每个ring buffer保留了之前几次某种类型的通知。指定ring buffer的详细信息将在下面描述。



[b]RING_BUFFER_RESOURCE_MONITOR[/b]

你可以使用从资源监视器的通知识别内存改变的状态。在内部,SQL Server有一个监视不同内存压力的架构。当内存状态改变,资源监视器任务生成一个通知。这个通知用于内部组件根据内存状态调整它们内存使用并通过sys.dm_os_ring_buffers DMV来暴露,如下列代码所示。



select record

from sys.dm_os_ring_buffers

where ring_buffer_type = 'RING_BUFFER_RESOURCE_MONITOR'

结果类似于


-- confers no rights.

-- Use of included script samples are subject to the terms specified at

-- http://www.microsoft.com/info/cpyright.htm

--

-- this proc will create waitstats report listing wait types by

-- percentage.

-- (1) total wait time is the sum of resource & signal waits,

-- @report_format='all' reports resource & signal

-- (2) Basics of execution model (simplified)

-- a. spid is running then needs unavailable resource, moves to

-- resource wait list at time T0

-- b. a signal indicates resource available, spid moves to

-- runnable queue at time T1

-- c. spid awaits running status until T2 as cpu works its way

-- through runnable queue in order of arrival

-- (3) resource wait time is the actual time waiting for the

-- resource to be available, T1-T0

-- (4) signal wait time is the time it takes from the point the

-- resource is available (T1)

-- to the point in which the process is running again at T2.

-- Thus, signal waits are T2-T1

-- (5) Key questions: Are Resource and Signal time significant?

-- a. Highest waits indicate the bottleneck you need to solve

-- for scalability

-- b. Generally if you have LOW% SIGNAL WAITS, the CPU is

-- handling the workload e.g. spids spend move through

-- runnable queue quickly

-- c. HIGH % SIGNAL WAITS indicates CPU can't keep up,

-- significant time for spids to move up the runnable queue

-- to reach running status

-- (6) This proc can be run when track_waitstats is executing

--

-- Revision 4/19/2005

-- (1) add computation for CPU Resource Waits = Sum(signal waits /

-- total waits)

-- (2) add @report_order parm to allow sorting by resource, signal

-- or total waits

--

set nocount on



declare @now datetime,

@totalwait numeric(20,1),

@totalsignalwait numeric(20,1),

@totalresourcewait numeric(20,1),

@endtime datetime,@begintime datetime,

@hr int,

@min int,

@sec int



if not exists (select 1

from sysobjects

where id = object_id ( N'[dbo].[waitstats]') and

OBJECTPROPERTY(id, N'IsUserTable') = 1)

begin

raiserror('Error [dbo].[waitstats] table does not exist',

16, 1) with nowait

return

end



if lower(@report_format) not in ('all','detail','simple')

begin

raiserror ('@report_format must be either ''all'',

''detail'', or ''simple''',16,1) with nowait

return

end

if lower(@report_order) not in ('resource','signal','total')

begin

raiserror ('@report_order must be either ''resource'',

''signal'', or ''total''',16,1) with nowait

return

end

if lower(@report_format) = 'simple' and lower(@report_order) <> 'total'

begin

raiserror ('@report_format is simple so order defaults to ''total''',

16,1) with nowait

select @report_order = 'total'

end





select

@now=max(now),

@begintime=min(now),

@endtime=max(now)

from [dbo].[waitstats]

where [wait_type] = 'Total'



--- subtract waitfor, sleep, and resource_queue from Total

select @totalwait = sum([wait_time_ms]) + 1, @totalsignalwait = sum([signal_wait_time_ms]) + 1

from waitstats

where [wait_type] not in (

'CLR_SEMAPHORE',

'LAZYWRITER_SLEEP',

'RESOURCE_QUEUE',

'SLEEP_TASK',

'SLEEP_SYSTEMTASK',

'Total' ,'WAITFOR',

'***total***') and

now = @now



select @totalresourcewait = 1 + @totalwait - @totalsignalwait



-- insert adjusted totals, rank by percentage descending

delete waitstats

where [wait_type] = '***total***' and

now = @now



insert into waitstats

select

'***total***',

0,@totalwait,

0,

@totalsignalwait,

@now



select 'start time'=@begintime,'end time'=@endtime,

'duration (hh:mm:ss:ms)'=convert(varchar(50),@endtime-@begintime,14),

'report format'=@report_format, 'report order'=@report_order



if lower(@report_format) in ('all','detail')

begin

----- format=detail, column order is resource, signal, total. order by resource desc

if lower(@report_order) = 'resource'

select [wait_type],[waiting_tasks_count],

'Resource wt (T1-T0)'=[wait_time_ms]-[signal_wait_time_ms],

'res_wt_%'=cast (100*([wait_time_ms] -

[signal_wait_time_ms]) /@totalresourcewait as numeric(20,1)),

'Signal wt (T2-T1)'=[signal_wait_time_ms],

'sig_wt_%'=cast (100*[signal_wait_time_ms]/@totalsignalwait as numeric(20,1)),

'Total wt (T2-T0)'=[wait_time_ms],

'wt_%'=cast (100*[wait_time_ms]/@totalwait as numeric(20,1))

from waitstats

where [wait_type] not in (

'CLR_SEMAPHORE',

'LAZYWRITER_SLEEP',

'RESOURCE_QUEUE',

'SLEEP_TASK',

'SLEEP_SYSTEMTASK',

'Total',

'WAITFOR') and

now = @now

order by 'res_wt_%' desc



----- format=detail, column order signal, resource, total. order by signal desc

if lower(@report_order) = 'signal'

select [wait_type],

[waiting_tasks_count],

'Signal wt (T2-T1)'=[signal_wait_time_ms],

'sig_wt_%'=cast (100*[signal_wait_time_ms]/@totalsignalwait as numeric(20,1)),

'Resource wt (T1-T0)'=[wait_time_ms]-[signal_wait_time_ms],

'res_wt_%'=cast (100*([wait_time_ms] -

[signal_wait_time_ms]) /@totalresourcewait as numeric(20,1)),

'Total wt (T2-T0)'=[wait_time_ms],

'wt_%'=cast (100*[wait_time_ms]/@totalwait as numeric(20,1))

from waitstats

where [wait_type] not in (

'CLR_SEMAPHORE',

'LAZYWRITER_SLEEP',

'RESOURCE_QUEUE',

'SLEEP_TASK',

'SLEEP_SYSTEMTASK',

'Total',

'WAITFOR') and

now = @now

order by 'sig_wt_%' desc



----- format=detail, column order total, resource, signal. order by total desc

if lower(@report_order) = 'total'

select

[wait_type],

[waiting_tasks_count],

'Total wt (T2-T0)'=[wait_time_ms],

'wt_%'=cast (100*[wait_time_ms]/@totalwait as numeric(20,1)),

'Resource wt (T1-T0)'=[wait_time_ms]-[signal_wait_time_ms],

'res_wt_%'=cast (100*([wait_time_ms] -

[signal_wait_time_ms]) /@totalresourcewait as numeric(20,1)),

'Signal wt (T2-T1)'=[signal_wait_time_ms],

'sig_wt_%'=cast (100*[signal_wait_time_ms]/@totalsignalwait as numeric(20,1))

from waitstats

where [wait_type] not in (

'CLR_SEMAPHORE',

'LAZYWRITER_SLEEP',

'RESOURCE_QUEUE',

'SLEEP_TASK',

'SLEEP_SYSTEMTASK',

'Total',

'WAITFOR') and

now = @now

order by 'wt_%' desc

end

else

---- simple format, total waits only

select

[wait_type],

[wait_time_ms],

percentage=cast (100*[wait_time_ms]/@totalwait as numeric(20,1))

from waitstats

where [wait_type] not in (

'CLR_SEMAPHORE',

'LAZYWRITER_SLEEP',

'RESOURCE_QUEUE',

'SLEEP_TASK',

'SLEEP_SYSTEMTASK',

'Total',

'WAITFOR') and

now = @now

order by percentage desc





---- compute cpu resource waits

select

'total waits'=[wait_time_ms],

'total signal=CPU waits'=[signal_wait_time_ms],

'CPU resource waits % = signal waits / total waits'=

cast (100*[signal_wait_time_ms]/[wait_time_ms] as numeric(20,1)),

now

from [dbo].[waitstats]

where [wait_type] = '***total***'

order by now

go





declare @now datetime

select @now = getdate()

select getdate()


相关文章: