从等待类型找出性能瓶颈

1. Resource_semaphore等待:

SQL Server 有两类RESOURCE_SEMAPHORE 等待事件: RESOURCE_SEMAPHORE 和 RESOURCE_SEMAHPORE_QUERY_COMPILE.Resource_SEMAPHORE是指一条或多条语句在执行的时候等待memory的分配。RESOURCE_SEMAHPORE_QUERY_COMPILE是指一条语句在编译的时候等待memory的情况。

  首先我们来弄清楚一下什么是Resource_semaphore等待。当SQLServer收到一个用户请求(或者查询时)。首先会创建一个编译后的计划,然后在这个基础上创建一个执行计划。当SQLServer创建一个编译后的计划时,它会计算两个内存授予参数,分别为:请求内存(required memory)和额外内存(additional memory),请求内存是运行排序和hash连接的所需最少内存,之所谓成为“请求”,是因为查询不需要在一开始就申请这部分的内存。而额外内存是存放临时数据到内存中所需的那部分内存。如果没有足够的内存,查询所需的数据将会存到硬盘当中。首先,服务器会计算运行特定查询所需要的内存。这部分通常等于请求内存和额外内存的总和。但当实例使用并行执行时,所需的内存为(请求内存*并行度)加上额外内存的总和。服务器会检查是否有足够的内存来运行每个查询,然后会降低额外内存的量,知道所有总内存需求量刚好达到内存的限制量。这部分修改后的内存成为需求内存(requested memory)。在SQLServer内不能,有一个叫Resource Semaphore的设置,用于授予需求内存用于查询。当查询没有得到足够的内存,就会把等待状态改为:Resource_Semaphore。可以从sysprocesses系统表或者sys.dm_exec_request DMV中查询。当Resource_semaphore接受一个新的请求时,首先检查是否有查询还在等待中,如果发现有,那么会把这个新请求放到先进先出的队列中,Resource Semaphore会尝试对未等待的查询授予内存,这部分内存可能是之前的查询执行完毕后返回的内存。如果发现有足够的内存,那么就会把内存赋予给处于Resource Semaphore等待状态的查询,让其开始运行。如果不够,那么会把查询放入等待队列并标记为Resource_Semaphore等待。因此,看这个等待状态可以发现内存存在压力。

总结当由于存在其他并发查询而无法立即批准查询内存请求时出现。等待时间较长或等待次数较多可能指示并发查询的数量过多或内存请求的数量过多。即内存存在内存的压力,会造成页面I/O等待。

 可以通过动态视图DV查看具体是哪些语句造成的

1.筛选Resource_Semaphore等待的数据:
SELECT  *  FROM    sys.SYSPROCESSES  WHERE   lastwaittype = 'RESOURCE_SEMAPHORE'  ORDER BY lastwaittype  

/*******************************2.查看已分配到内存的查询的目前状态,和未被分配内存的查询的数量***********************
dm_exec_query_resource_semaphores:提供一般的查询执行内存状态,并允许您确定系统是否可以访问足够的内存
resource_semaphore_id:资源信号的非唯一标识。0为常规资源信号量,1为小型查询资源信号量。 
grantee_count是已经分配了内存的总查询数量,而waiter_count是在队列中等待授予内存的总查询数量    ********************/
SELECT  *  FROM  sys.dm_exec_query_resource_semaphores  

/******************************3.获得在等待队列中的查询所需要内存的详细信息**********************************
dm_exec_query_memory_grants:返回所有已请求并正在等待内存授予或已被授予内存授予的查询的信息***********************
requested_memory_kb:请求的内存总量            
granted_memory_kb:实际授予的内存总量 实际授予的内存总量,对于一个典型的情况,这个值应该和requested_memory_kb一样 
required_memory_kb:运行此查询所需的最小内存  ***********************************************************/
select * from sys.dm_exec_query_memory_grants
 
 --4.查看对应的执行计划
SELECT TOP 10  * 
FROM   sys.dm_exec_query_memory_grants a
CROSS APPLY sys.dm_exec_sql_text(a.sql_handle) 

 

2. ASYNC_NETWORK_IO等待

此等待状态出现在SQLServer已经把数据准备好,但是网络没有足够的发送速度跟上,所以SQLServer的数据没地方存放。

  1. 出现这种情况一般不是数据库的问题,调整数据库配置不会有大的帮助。
  2. 网络层的瓶颈当然是一个可能的原因:对此要考虑是否真有必要返回那么多数据?
  3. 应用程序端的性能问题,也会导致SQLServer里的ASYNC_NETWORK_IO等待。如果见到了这个类型的等待,就要检查应用程序的健康状况,也要检查应用是否有必要想SQLServer申请这么大的结果集。

将结果集返回给客户端,得到结果后,SQLServer会把结果集放到输出缓存中,等客户端把结果集全部取走。指令才结束。如果数据集太大,会导致网络交互太多。此时容易出现:ASYNC_NETWORK_IO等待状态。这个等待类型表示并非SQL Server的问题,绝大多数情况下也与网络问题无关(很多时候大家都认为是网络问题),一个简单的测试方式是从客户端ping一下服务端,如果延迟接近sys.dm_exec_requests中wait_time的平均值则证明确实与网络相关(很多时候都只是正常的网络延迟,并不是网络故障)。

针对此等待事件一般无需对SQL代码做什么改动,引发此问题的原因基本都是由于来源于客户端,例如
  • 客户端代码使用RBAR方式处理数据集,每次只从结果集拉取一条数据,而不是全部获取完毕后再处理。
  • 客户端所在的服务器有某些性能问题,导致客户端运作缓慢。
  • 客户端运行在配置错误或者过载的虚拟机上,总之也是服务器本身的问题。
在数据库服务端,就我所知唯一可能的原因就是使用了MARS的大结果集引起的。(其实就是因为结果集太大).你可以很轻易的通过在数据库服务器上使用本机名登录的方式,运行一个获取大结果集的查询,来验证这个等待事件是否会出现。
查是否是因为网络数据传输问题导致:
在SSMS工具栏,勾选“Include Client Statistics”或使用快捷键SHIFT+ALT+S,然后执行SQL语句,我们也可以将客户端处理时间权当网络数据传输时间SQL SERVER的请求接收和数据输出的一个大致流程图,
当客户端发送请求开始,当服务器接收客户端发来的最后一个TDS包,数据库引擎开始处理请求,请求完成后,将数据发送给客户端,从图中可以看出,客户端接收服务器端返回的数据也是需要一个过程的(或者说时间).
我们在SQL优化过程中,如果一个SQL出现性能问题时,我们应该站在一个全局的角度来分析问题,从CPU资源、网络带宽、磁盘IO、执行计划等多方面来分析,这样才能有助于你分析、定位问题根源,
而不要只要SQL响应很慢时,就一味条件反射式先入为主:这是数据库问题。数据库也不能老背这个黑锅。
那么回到如何优化这个SQL的问题上来,我们可以从下面几个方面来进行优化。
  • SQL只取必须的字段数据
  • 查应用是否有必要想SQLServer申请这么大的结果集

3. CXPACKET等待

  CXPACKET 已经成为所有等待类型中最常见的一种了,其官方解释如下:当尝试同步查询处理器交换迭代器时出现。如果针对该等待类型的争用成为问题时,可以考虑降低并行度当为SQL查询创建一个并行操作时,会有多个线程去执行这个查询。每个查询处理不同的数据集或行集。因为某些原因,一个或多个线程滞后,而产生了CXPACKET等待状态。有一个组织/协调(organizer/coordinator)线程(Thread 0),它需要等待所有线程完成并聚合数据再呈现给客户端组织线程必须等待所有线程完成处理才能进行下一步。由于组织线程等待缓慢的线程完成处理所产生的等待,就叫CXPACKET等待。请注意,并不是所有的CXPACKET等待类型都是不好的事情。你也许会遇某个CXPACKET等待是完全有意义的案例,有时它也是不可避免的。如果你在任何查询上禁止此种等待,那么查询也许会变慢,因为不能为它执行并行操作。
减少CXPACKET等待:
    我们不能抛开服务器负载类型来讨论减少CXPACKET等待。
 
 OLTP: 在纯OLTP系统上,它的事务较短,查询也不长,但是通常很快速。设置“Maximum degree of Parallelism”(MAXDOP)为1。
            这样做可以确保查询永远不必使用并行方式运行,并且不会导致更多的数据库引擎开销。
 
EXEC sys.sp_configure N'cost threshold for parallelism', N'1'
GO
RECONFIGURE WITH OVERRIDE
GO

 Data-warehousing / Reporting server: 因为查询执行时间一般较长,建议设置“Maximum degree of Parallelism”(MAXDOP)为0。 这样大多数查询将会利用并行处理,执行时间较长的查询也会受益于多处理器而提高性能。

EXEC sys.sp_configure N'cost threshold for parallelism', N'0'
GO
RECONFIGURE WITH OVERRIDE
GO

 

Mixed System (OLTP & OLAP):这样环境会是一个挑战,必须找到正确的平衡点。我采取了非常简单的方法。我设置“Maximum degree of Parallelism”(MAXDOP)为2,这样意味着查询仍会使用并行操作但是仅利用2颗CPU。

               然而,我把“并行查询阀值”(cost threshold for parallelism)设置为较高的值,这样的话,不是所有的查询都有资格使用并行,除了那些查询成本较高的查询。在一个即有OLTP查询又有报表服务器的系统上,

               我发现这样做运行得很好。在这里我将会设置“‘Cost Threshold for Parallelism’”为25,你可以选择任何值。但你只能通过在系统上做实验来找到合适的值。在下面的脚本中,我设置“Max Degree of Parallelism”为2,

               这样的话,那些具有较高成本的查询(这里是25),将会在2颗CPU上执行并行查询。同时,不管服务器有多少颗CPU,查询只会选择两颗CPU来执行。       

EXEC sys.sp_configure N'cost threshold for parallelism', N'25'
GO
EXEC sys.sp_configure N'max degree of parallelism', N'2'
GO
RECONFIGURE WITH OVERRIDE

导致CXPACKET等待高的原因有很多,不能盲目地修改 MAXDOP的值或修改实例级别的最大并发度

可能原因有:

  • 统计过期导致生成低效的执行计划
  • 缺乏索引导致表扫描
  • 中间结果集无法预估结果集行数,导致执行计划低效(即统计信息不准确)
  • 某个线程因为其他资源被堵塞

解决方案:

  • 检查执行计划是否高效
  • 修改语句的并发度
  • 修改实例级别的并发度

补充:建议将MAXDOP的值设置为小于逻辑CPU的数,以避免单个查询阻塞所有请求。

总结:CXPACKET 这个等待可以简单理解成CPU相关的等待,主要发生在并行计划中。由于并行计划需要协同多个task同时工作,那么“协同”分配等等操作的时候出现的就是这个等待。如果 CXPACKET 在你系统中是最为严重的等待,

      这时候一般的表现是你的CPU很高。一般建议系统如果超过32个CPU 那么并行度设置成8或者4,如果系统中都是特别短小且频繁的语句建议设置成1(取消语句并行,要慎重真的符合你的场景才好), 并行开销的阀值,

   主要控制SQL优化器何时选用并行计划,建议默认值,此值设置的越小优化器越容易选择并行计划。

 

  

4.Latch_Ex等待

当某个数据库文件空间用尽,做自动增长的时候,同一时间点只能有一个用户人员可以做文件自动增长动作,其他任务必须等待,此时会出现Latch资源的等待。使用sp_helpdb查看业务数据库时发现:该数据库设置不合理,数据文件每次增长2M,日志文件每次增长10%,且分别设置了最大限制。修改为每次增长200M,不限制大小,系统恢复正常,问题解决。

 

5. IO

IO_COMPLETION和PAGEIOLATCH_和WRITELOG 这三个等待是最为常见的和磁盘相关的等待。他们的不同点是 IO_COMPLETION  主要针对非数据页 I/O ,如备份操作所需的磁盘交互。
PAGEIOLATCH_ 是数据页相关的磁盘等待。WRITELOG 是日志相关。如果系统中这三个等待是主要等待,说明系统磁盘存在压力或已经成为瓶颈。
操作系统CPU操作的任何数据都是从内存中读取的,也就是说读取数据要经过这样的一条路:磁盘中 ——>  内存中 ——>  最终使用  
这里的PAGEIOLATCH_ 就是发生在 磁盘中 ——>  内存中  ;以读取为例:要读取的数据页不在内存中,所以就要去磁盘上读取这部分数据页,去磁盘读取数据的时候就会产生PAGEIOLATCH_的相关等待,
如果磁盘压力大,长时间不能反回数据,那么PAGEIOLATCH_的时间也会越长,语句执行的时间也会越长。

PAGEIOLATCH_ 等待:   

缓存在内存缓存池区域里的数据页面,和磁盘上数据文件的数据页面进行交互时,为了保证不会有多个用户同时读取/修改内存里的数据页面时,sql server会像对待表格里的数据一样,
对内存中的页面实行加锁的机制,以同步多用户并发处理,不同的是,在这里,sql server加的是latch(轻量级的锁),而不是lock.例如,当sql server将数据页面从数据文件读到内存,为了防止
其他用户对内存里的同一个数据页面进行访问,sql server会在内存的数据页面上加一个排它的latch,而当有任务要读缓存在内存里的页面时,会申请一个共享的latch,像lock一样,latch也会出现阻塞的现象.
这里来举一个最容易发生等待的‘pageiolatch_sh’,看看这种等待是怎么发生的:
         1.  有一个用户请求,须读取整张X表,由work X执行.
         2.  work X在执行表扫描的过程中发现它要读取数据页面1:100
         3.  sql server发现页面1:100并不在内存中的数据缓存里
         4.  sql server 在缓存池找到一个页面的空间,在上面申请一个EX(排它)的latch,防止数据从磁盘读出来前,有别人也来读取或修改这个页面.
         5.  work X发起一个异步请求,要求从数据文件里读出页面1:100.
         6.  由于是异步I/O,work X还可以做其他事情,而下面要做的,就是读出内存的页面1:100,读取的动作需要申请一个SH(共享)的latch.
         7.  由于work X之前已经申请了EX Latch还没释放,所以这个SH latch将被阻塞住,work X被自己阻塞,等待的资源就是pageiolatch_sh.
         8.  当异步I/O结束后,系统会通知work X,你要的数据已经存到内存了.
         9.  这时候EX latch将被释放,接着work X得到了它申请的SH latch.
         10.  数据也1:100终于被worh X读到,读取工作结束,work可以继续下面的工作.
 
 由此可以看出,在发生pageiolatch类型的等待时,sql server一定是在等待某个I/O动作的完成,所以如果一个sql server经常出现这一类等待,说明磁盘的速度不能满足sql server的需要,它已经成为了sql server的一个瓶颈.

 pageiolatch_x类型等待最常见的是两类,pageiolatch_sh和pageiolatch_ex。pageiolatch_sh经常发生在用户正想要访问一个数据页面,而同时 sql server却要把这个页面从磁盘读到内存,如果这个页面是

用户经常有可能访问到的,那么说到底,问题是因为内存不够大,没能将数据页面缓存在内存里,  所以,往往是现有内存的压力,触发sql server做了很多读取页面的工作,才引发了磁盘读的瓶颈,这里的磁盘瓶颈

常常是内存瓶颈的副产品.      而pageiolatch_ex常常是发生在用户对数据页面做了修改,sql server要向磁盘回写的时候,基本意味着磁盘的写入速度明显跟不上,这里和内存瓶颈没有直接的联系.

 和磁盘有关的另一个等待状态是writelog,说明任务当前正在等待将日志记录写入日志文件,出现这种等待,也意味着磁盘的写入速度明显跟不上。

可能导致的原因:

  • IO系统的确存在问题,存在IO瓶颈
  • 执行计划不佳导致扫描  
  • 内存存在压力导致页在内存中的存活时间过短   

解决方案:

  • 检查执行计划
  • 检查是否存在内存压力
  • 检查是否存在其他应用导致IO压力
  • 检查存储是否满足需求

 

注 : 常见的有PAGEIOLATCH_SH和PAGEIOLATCH_EX,在数据页从磁盘读取到内存中发生等待,SH表示数据页用于读取,EX表示数据页用于修改当你的系统出现大量的 PAGEIOLATCH_ 类等待,说明你磁盘可能存在压力(磁盘速度不能满足当前业务需求)或你的内存不够用,不能缓存业务常用数据而经常要与磁盘交互! writelog是跟磁盘有关的另一个等待状态,正在等待写日志记录,意味着写入速度也明显跟不上。而速度跟不上一般有两种情况:磁盘压力大响应时间长或真的速度不能满足读写需要。
 可能原因:
1>导致大量事务日志的操作产生,比如索引维护
2>事务并发度较高
3>IO系统瓶颈
4>频繁页拆分

        解决方案

1>查看LOGBUFFER等待,是否存在对日志缓冲区的争抢
2>查看日志所在磁盘是否存在队列
3>查看事务平均大小,是否存在小事务频繁操作,将小事务合并为大事务
4>删除无用索引
5>业务拆分,由多个数据库来承担压力
6>提高日志磁盘的性能
7>修改索引,减少页分裂

 

6. pagelatch

PAGELATCH_和 上面讲述的PAGEIOLATCH_ 看似很像,但中间少了 IO 这个关键。磁盘中 ——>  内存中 ——>  最终使用.
磁盘中——>内存中 的等待为PAGEIOLATCH_   而 内存中——> 最终使用 的等待为 PAGELATCH_等待。当数据已经在内存中的时候,SQL SERVER 想要使用这个数据页就要给这个数据页加锁。
当等待中出现很多PAGELATCH_ 等待,那么可以说明:
  • SQL Server没有明显的内存和磁盘瓶颈。
  • 应用程序发来大量的并发语句在修改同一张表格里的记录,而表格架构设计以及用户业务逻辑使得这些修改都集中在同一个页面,或者数量不多的几个页面上。这些页面有的时候也被称为Hot Page。这样的瓶颈通常只会发生在并发用户比较多的、典型的OLTP系统上。
  • 这种瓶颈是无法通过提高硬件配置解决的,只有通过修改表格设计或者业务逻辑,让修改分散到尽可能多的页面上,才能提高并发性能。
 
   sql server的数据页面大致分成3个部分:页头,页尾偏移量,数据存储部分.在页头部分,会记录页面属性,包括页面编号,当前页面空闲部分的起始位置在哪.

    这样sql server在要插入新数据的时候,就能很快的找到开始插入的位置,而页尾的偏移记录了每一条数据行的起始位置,这样sql server在找每一条记录的时候,
    就能很快找到.当页面里的数据行发生变化的时候,sql server不但要去修改数据本身,还要修改这些偏移量的值.以保证sql server能够继续准确的管理数据页面的每一行.

     数据的修改都集中在一个页面,或是数量不多的几个页面上,这些页面有时候被称为hot page,这样的瓶颈只会发生在用户比较多的、典型的OLTP系统上。
    避免hot page的pagelatch资源瓶颈:
  • 最简单的办法,是换一个数据列建聚集索引,而不要建在identity的字段上,专业表格里的数据就按照其他方式排序,同一时间的插入就有机会分散到不同的页面上.
  • 如果必须在identity列上建聚集索引,建议根据其他某个数据列在表格上建立若干个分区,把一个表格分成若干个分区,可以使得接受新数据的页面增多.
    解决页拆分引起的问题:
  • GUID类型的索引键造成的页拆分,设置合适的填充因子或修改GUID键
  • 数据变化过大引起的页拆分,将变化较大(长度)且变化频率较高的列拆分到其他表
  • 控制索引键的长度

    解决高并发插入自增表导致的插入热点:

  • 使用随机或组合键来将数据分散到表中各个部分而不是尾部
  • 修改架构,将数据插入到多个表中或多个数据库中



7. LCK_
LCK_等待类型很多,如果这种等待在系统中大量存在,可以说明,系统语句间的相互阻塞严重。如大家都知道的当你update一张表的时候,你的select会被阻塞直到update完成。
由于资源被加锁而导致其线程因锁不兼容造成阻塞,
造成的原因:
  • 不合理的事务隔离级别
  • 过大的事务长期占有某些资源
  • 执行效率不高的语句
解决此类等待的主要方法:
  • 语句优化,让语句执行的更快,减少等待时间。
  • 采用批量操作代替循环方式。
  • 尽量减少事务的长度。
  • 尝试降低事务隔离级别。
  • 述都不能缓解...请选用读写分离。

 

8. sos scheduler yield

SQL Server的thread scheduling(线程调度)是由SQL Server自己来完成的。它分为三个状态,running, suspended, runnable。我画了一个图来解释这三个状态:

  

每一个thread(线程)在cpu上执行时候都是有一个quantum(时间片)的,当你的quantum用完了而你的任务还没有结束,这个thread就会自己释放CPU,直接进入runnable队列中排队等待下一次执行,这个直接释放CPU再次等待的过程叫yield(放弃)。我们知道SQL Server中所有的wait都是有一个类型的,这种自己yield CPU并且等待下一次执行的类型就是sos scheduler yield。如果一个thread不yield,那么它的工作就会一次性地被CPU执行完,而其他thread就没有执行机会,这显然是不好的。明白了什么是sos scheduler yield,我们来看看什么导致了scheduler yield。我们已经知道scheduler yield是说一个thread在CPU上疯狂执行直到把自己的这一次的quantum都用完了,也就是说这个thread在执行的过程中需要的所有的resource都是available的,不然它就会进入suspended状态。那么我们想想什么操作会有这样的表现呢?对了就是table scan或者index scan!当一个很大的scan操作需要访问的所有page都在内存并且也不存在page latch无法获得的问题时,这个thread就尽情地scan这些page,但是由于page太多了,quantum用完了都还没有scan完,所以只有忍痛yield CPU等待下一次执行机会。这就是最常见的sos scheduler yield的原因。当然还有其他原因,比如确实是你的CPU不够强大,已经成为系统的瓶颈。建议检查你的stored procedure,看看有没有big scan,是不是需要创建index来speed up the query。
 

9. REDO_THREAD_PENDING_WORK

 当SQL正在等待生成一些新的日志数据或者当试图从日志文件中读取数据时有一些延迟时,就会发生这种等待类型。日志文件的内部和外部碎片或缓存可能会导致此等待类型。此等待一般出现在集群的辅助服务器,如果是异步模式,应该对性能影响不大,如果是同步模式,就会导致主节点的事务锁延迟过长,系统变卡。 此等待归根到底为等待读日志内容产生的。

posted on 2018-03-15 20:48  Mr.念语  阅读(804)  评论(0编辑  收藏  举报