2005、2008提供了以下三个视图供获取连接详细信息:
DMV |
用处 |
参考 |
Sys.dm_exec_requests |
返回有关在SQL Server中执行的每个请求的信息,包括当前的等待状态 |
https://docs.microsoft.com/zh-cn/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-requests-transact-sql?view=sql-server-ver15 |
Sys.dm_exec_sessions |
对于每个通过身份验证的会话都返回相应的一行。它是服务器范围的视图,显示所有活动用户连接和内部任务信息,还可查看当前系统负荷并标识相关会话。 |
https://docs.microsoft.com/zh-cn/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-sessions-transact-sql?view=sql-server-ver15 |
Sys.dm_exec_connections |
返回与SQL Server 实例建立的每个连接的详细信息(如验证方式、endpoint等) |
https://docs.microsoft.com/zh-cn/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-connections-transact-sql?view=sql-server-ver15 |
注意:sys.sysprocesses是为了向后兼容,不支持多个活动结果集(MARS,应该了解同时开启多个结果集),建议使用以上3个DMV。
sys.dm_os_wait_stats可以返回SQL Server启动以来所有等待状态总的等待数和等待时间,是个累积值。
列名 | 描述 |
---|---|
wait_type | 等待类型的名称。有关详细信息,请参阅"等待类型"。 |
waiting_tasks_count | 该等待类型的等待数,该计数器在每开始一个等待时便会增加。 |
wait_time_ms | 该等待类型的总等待时间(毫秒)。该时间包括 signal_wait_time_ms。 |
max_wait_time_ms | 该等待类型的最长等待时间。 |
signal_wait_time_ms | 正在等待的线程从收到信号通知到其开始运行之间的时间差。 |
pdw_node_id | 此分发所在节点的标识符。 |
下面整理一些工作中最常见的几种等待类型,完整版参考
https://docs.microsoft.com/zh-cn/sql/relational-databases/system-dynamic-management-views/sys-dm-os-wait-stats-transact-sql?view=sql-server-ver15
如果SQL Server有阻塞发生,经常会看到以“LCK_”开头的等待状态:
等待名称 |
说明 |
LCK_M_BU |
正在等待获取大容量更新锁(BU) |
LCK_M_IS |
等待获取意向共享锁(IS) |
LCK_M_IU |
等待获取意向更新锁(IU) |
LCK_M_IX |
等待意向排它锁(IX) |
LCK_M_RIn_NL |
等待获取当前键值上的NULL锁以及当前键和上一个键之间的插入范围锁 |
LCK_M_RIn_S |
等待获取当前键值上的共享锁以及当前键和上一个键之间的插入范围锁 |
LCK_M_RIn_U |
等待获取当前键值上的更新锁以及当前键和上一个键之间的插入范围锁 |
LCK_M_RIn_X |
等待获取当前键值上的排他锁以及当前键和上一个键之间的插入范围锁 |
LCK_M_RS_S |
等待获取当前键值上的共享锁以及当前键和上一个键之间的共享范围锁 |
LCK_M_RS_U |
等待获取当前键值上的更新锁以及当前键和上一个键之间的共享范围锁 |
LCK_M_RX_S |
等待获取当前键值上的共享锁以及当前键和上一个键之间的排他范围锁 |
LCK_M_RX_S |
等待获取当前键值上的共享锁以及当前键和上一个键之间的排他范围锁 |
LCK_M_RX_U |
等待获取当前键值上的更新锁以及当前键和上一个键之间的排他范围锁 |
LCK_M_RX_X |
等待获取当前键值上的排他锁以及当前键和上一个键之间的排他范围锁 |
LCK_M_S |
等待获取共享锁 |
LCK_M_SCH_M |
等待架构修改锁 |
LCK_M_SCH_S |
等待获取架构共享锁 |
LCK_M_SIU |
等待共享意向更新锁 |
LCK_M_SIX |
等待获取共享意向排他锁 |
LCK_M_U |
等待更新锁 |
LCK_M_UIX |
等待更新意向排他锁 |
LCK_M_X |
等待排他锁 |
这类等待发生在内存缓冲池中的数据页要与磁盘上数据文件的数据页进行交互时,所以闩锁名称中包含IO。为保证不会有多个用户同时读取/修改内存中的数据页,SQL Server会对内存中的数据页加闩锁,称为PAGEIOLATCH_%。
根据不同的等待资源,%的值也不同,可能出现以下不同的等待:
这里以最容易发生的PAGEIOLATCH_SH为例,看看这种等待是怎么发生的。
1)有一个用户请求,必须读取整张X表,由Worker X执行
2)Worker X在表扫描过程中发现要读取page 1:100
3)sqlserver发现page 1:100不在内存的数据缓冲池里
4)sqlserver在数据缓冲池找到一个page的空间,Worker X在上面申请EX模式latch,防止将数据加载入内存前有人读写该page
5)Worker X发起一个异步的IO请求,要求从数据文件中读取page 1:100
6)Worker X需要从内存中读取page 1:100,读取动作需要申请SH模式latch
7)由于Worker X之前申请的EX模式latch还没释放,因此申请SH模式latch的操作将被阻塞(自己阻塞自己)。由于Worker X在等待获取SH模式latch,等待类型即为PAGEIOLATCH_SH
8)当异步IO结束后,系统会通知Worker X,page 1:100已写入内存
9)Worker X释放EX模式latch,此时即可获得SH模式latch
10)Worker X在内存中读page 1:100,读取结束后,进行之后的工作
可以看到,当发生PAGEIOLATCH类型的等待时,SQL Server一定是在等待某个I/O动作的完成。如果经常出现这类等待,说明磁盘速度不能满足要求,已经成为SQL Server的瓶颈。
更要强调的是,PAGEIOLATCH等待最常见分两大类:PAGEIOLATCH_SH和PAGEIOLATCH_EX
PAGEIOLATCH_SH
经常发生在用户想要访问一个数据页,但该数据页不在缓存中,SQL Server需要先把该页从磁盘读往内存。如果这个问题经常发生,说明内存很可能不够大,触发了SQL Server做了很多读取页面的工作,进而引发了磁盘读的瓶颈。此时主要是内存有瓶颈,磁盘只是内存压力的副产品。
PAGEIOLATCH_EX
经常发生在用户对数据页面做了修改,SQL Server要向磁盘回写时,意味着磁盘写的速度跟不上,和内存没直接关系。
WRITELOG
和磁盘有关的另一个等待状态是WRITELOG,说明任务当前正在等待将日志记录写入日志文件,通常也意味着磁盘写入速度跟不上。
首先注意PAGELATCH_%虽然跟PAGEIOLATCH_%名字很像,但它们的成因是完全不一样的,可以说除了名字像其他没啥像了(类似buffer busy wait和free buffer wait)。
SQLServer为了解决在插入数据时物理层的插入冲突,引入了PAGELATCH_%类的latch。当任务要修改page时,必须先申请一个EX的latch,只有得到这个latch,才能修改page内容。
数据页的修改都是在内存中完成,所以时间应该非常短,可以忽略不计,而PAGELATCH只在修改过程中才出现,所以正常生存周期应该很短。如果在数据库中经常遇到,说明:
详细参考
https://support.microsoft.com/en-us/help/4460004/how-to-resolve-last-page-insert-pagelatch-ex-contention-in-sql-server
这也是很常见的一种情况,数据库不仅在数据页修改时加latch,在数据文件的系统页(例如SGAM、PFS、GAM)发生修改时也会加latch,这些latch在某些情况下也会成为系统瓶颈。
在创建新表需要分配空间时,SQLServer同时要修改SGAM、PFS和GAM页面,把已分配的页面标志成已使用,所以这些页面都会有所修改。用户数据库一般不会出现频繁删表建表的情况,但在tempdb中,就很有可能出现。例如一个存储过程使用了临时表,这个存储过程被很多用户调用,就会有很多用户在tempdb创建临时表、处理完后又删除临时表,此时SGAM、PFS和GAM等页面上的latch就会成为系统瓶颈。
数据页的hot page可以通过调整表设计解决,那么系统页呢?
假设所有的cpu都在执行用户任务,最大任务数其实也不会超过服务器CPU核数。也就是说,同一时间最多只能有服务器CPU核数个任务并发访问tempdb,所以只要将任务尽量平均地分配到tempdb各个数据文件,就能尽量避免该问题。
总结一下,解决方法的要点是:
- 使用sp_helpfile可以查看数据文件情况
- 启用1117跟踪标记,sqlserver在自动增长数据文件时会尝试对所有文件进行同样大小的增长。
除了上面的最常见的资源等待,用户还可能遇到以下等待。
在sqlserver中,除了buffer latch,其他资源上也会出现latch。正常情况下,这些latch的申请和释放都是很快的,用户应该不会看到相应等待。
Latch等待常见原因:
1)某个先前的任务出现了访问越界异常(Access Violation),SQLServer强制终止了该任务,但是没有完全将它申请的资源释放干净,使某些latch成为孤儿,导致后面任何任务要申请同样的资源都会被阻塞。这类问题容易判断:只要打开sqlserver错误看看之前有没有出现过Access Violation即可,但是一般无法从用户层面解决,只能重启sqlserver服务。
2)同时发生其他资源瓶颈,如内存、线程调用、磁盘等,而latch等待只是一个衍生的等待。
3)当某个数据文件空间用尽,做自动增长的时候,其他任务必须等待,这时也会出现latch资源等待。
4)在一些特殊情况下,可能是SQLServer自己没有处理好,没有使用比较优化的算法(相当于bug),使得用户比较容易遇到等待,一些补丁就曾修复过这类问题。
可以看到,一般latch等待都是由其他问题衍生而来。当数据库中出现大量latch等待时,首先要检查SQLServer是否健康运行,看错误日志是否有出现过异常,是否有其他资源瓶颈。另外,将sqlserver升级到最新版本,通常是推荐的做法。
当sqlserver要返回结果集给客户端时,会先将结果集填充到输出缓存(output cache),同时网络层会开始将输出缓存里的数据打包,由客户端接收。如果sqlserver返回结果集的速度快过客户端接收速度,当结果集较大时,就会出现输出缓存被填满,sqlserver没地方填充结果集的问题。此时,任务就会进入ASYNC_NETWORK_IO的等待。注意这个等待一般不是数据库的问题,而是客户端由于各种各样的没有及时取走数据,因此调整数据库配置一般不会有大的帮助。
ASYNC_NETWORK_IO等待常见原因及优化建议:
当用户任务申请内存暂时申请不到时,会出现一些特殊的等待状态:
1)CMEMTHREAD
在一个时间点,只有一个连接能往一块缓存区申请/释放内存,当多个用户想要同时往同一块缓存区申请/释放内存时,只有一个连接能成功,其他的必须等待,等待的事件就是CMEMTHREAD。
这种等待通常只发生在并发特别高的sqlserver里,这些并发的连接通常大量使用每次执行都需编译的动态t-sql语句。
解决方法
2)SOS_RESERVEDMEMBLOCKLIST
如果sql语句包含大量参数、或者in子句包含大量值,它的执行计划可能超过8KB,需要申请multi-page区域来存储,当申请暂时不能得到满足时,等待的就是这个事件。这个问题更常见于32位机器,因为32位机器multi-page区域很小而且远小于buffer pool,如果multi-page有内存压力而buffer pool没有,并不会触发lazy writer刷数据清理内存。
解决方法
3)RESOURCE_SEMAPHORE_QUERY_COMPLIE
如果sql或存储过程过于复杂,编译所需的内存可能超乎你的想象。sqlserver为编译内存设了一个上限,当在编译的sql使用内存达到这个上限后,后面的语句只能等前面的语句编译完释放内存后才能继续编译,此时等待的事件就是RESOURCE_SEMAPHORE_QUERY_COMPLIE。
解决方法
对于繁忙的SQLServer,开启SQL Trace很可能对性能产生负面影响。如果经常出现这种等待,除非迫不得已,否则应该立刻停止SQL Trace。
这个问题最明显的特征就是大量任务处于runnable状态,等待类型为SOS_SCHEDULER_YIELD。如果出现这种状态,说明很多任务可以运行但没在运行,这同样会严重影响sqlserver性能。
任务的状态可以通过sys.dm_exec_requests的task_state列和sys.sysprocesses的status列查看,如果经常看到很多任务状态是runnable,就要严肃对待了。正常的SQLServer哪怕很忙,也不该经常看到runnable的任务,连running的状态都不应该很多。
如果遇到没有报17883/17884之类的警告,出现非常多的runnable任务通常可能有两种原因:
此时真的是没有足够cpu来及时处理用户的并发任务。应该优化最耗CPU资源的语句,或者紧急加CPU。
此时CPU并不高,小于50%,为什么还有CPU资源但任务无法运行呢?这是因为SQLServer除了lock和latch之外,还有一种更轻量级的spinlock(自旋锁)。对于一些通常很快就能得到和释放资源,SQLServer会选择让线程在cpu上稍微等待一下(即所谓的自旋),而不是cpu资源让出来。由于很快就能得到等待的资源,这样的处理能减少CPU上线程的切换,更高效。
运行以下语句可查看sqlserver在所有自旋锁上的等待次数
DBCC SQLPERF(SPINLOCKSTATS)
在2005版本64位SQL Server上,当内存比较充裕时,会缓存很多执行计划和很多执行计划安全上下文。在memory clerk里用TokenAndPermUserStore表示,当这段内存比较大时,并发用户会容易遇到一种叫MUTEX的自旋锁。可以参考:http://suppot.microsoft.com/kb/927396。这种问题只在安全上下文缓存得太多时才容易发生,所以定期运行以下语句能有效防止,并且对系统性能也没什么坏的影响:
DBCC FREESYSTEMCACHE(TokenAndPermUserStore)
也可以以-T4618和-T4610两个参数启动SQLServer,让SQLServer使用另一种缓存管理机制。
2008开始的版本对安全上下文管理已经改进,自旋锁问题基本已很少遇到。
最后总结一下一个用户请求在其生命周期中大致会经过哪些阶段,以及在各阶段可能需要等待的资源。
如果指令比较长或者比较多,客户端发指令的快慢就会影响SQLServer接收的速度,网络传输速度也会有影响。
对第一条,将上百个小指令合并成几个大的批处理可能提升性能;对第二条,把大的批处理/语句写成存储过程,减少网络传输,也会对性能有所帮助。
这一步耗费资源的种类比较多:
得到执行计划后就进入运行阶段,通常这步耗时最长,用到的资源也最多。在这一步要做很多事情:
需要的内存大小与sql的长度、复杂度有关,如果同时需要执行很多复杂sql,可能新sql在申请内存时就会遇到上面所说的内存等待。
要将数据从磁盘读到内存,如果发现内存没有足够的空闲页面存放所有数据,通常的等待状态是:PAGEIOLATCH_%。
这一步需要申请各种各样的锁,以实现事务隔离,可能会引起阻塞,遇到LCK_%相关等待。
这一步主要使用CPU。如果执行计划不够优化、计算量庞大,而又没有等待其他资源,通常CPU使用率会较高。
此时有可能出现tempdb瓶颈。
由于对象在内存中,不会触发磁盘写入,若大量修改同一页面,容易导致hot page,出现PAGELATCH_%等待。
以上动作都要先在sqlos中拿到Worker(Thread),这个Worker能还要排上scheduler,才能在CPU上运行。如果:
总之,从任务当前等待状态可以大概知道它当前运行到哪一步,也可以分析出sqlserver可能存在的资源瓶颈。在遇到性能问题时,先查看sys.dm_exec_requests这类DMV中各连接的状态,对定位问题很有帮助。
参考:《sqlserver 2012 实施与管理实战指南》