上一篇我们简单的介绍了,语句优化的三板斧,大部分语句三板斧过后,就算不成为法拉利也能是个宝马了。为了方便阅读给出系列文章的导读链接:
SQL SERVER全面优化-------Expert for SQL Server 诊断系列
本篇主要讲述几个常见的系统等待,透过这些等待,看看系统存在什么问题,怎么样解决这些问题。结合系统三巨头(CPU,内存,磁盘)综合展现系统问题和这些元素的联系。
首先我们举个例子:前文提到了,一个好的SQL语句就好比一辆时速180的好车,好的系统硬件(CPU,内存,磁盘)就好比平坦宽阔的马路。看似好车配好路,一定可以开的很快了!其实还忽略了一点!当你驾驶一辆法拉利跑在北京宽阔的三环上,就算你是老炮中的“三环十二少“,早高峰你能开到多少? 北京的早高峰!北京的早高峰!
这个例子就引出了系统阻塞和等待的概念,红灯(硬件等待,如IO等待),这就是正常的等待。另外一辆车在你前面不走了或开的很慢,那么你也只能等待(也可以说成你被他阻塞了)!
--------------博客地址---------------------------------------------------------------------------------------
Expert 诊断优化系列 http://www.cnblogs.com/double-K/
废话不多说,直接开整-----------------------------------------------------------------------------------------
如何判断任务或语句是否在等待?
SQL SERVER所有任务的状态大致有三类(sleeping、runnable或running)通过英文我想不用过多解释了。SQL DMV视图提供了以下三个视图详细查询语句的运行状态:
- Sys.dm_exec_requests :返回有关在SQL Server中执行的每个请求的信息,包括当前的等待状态
- Sys.dm_exec_sessions :对于每个通过身份验证的会话都返回相应的一行。此时图是服务器范围的视图。此视图首先可以查到服务器负荷
- Sys.dm_exec_connections : 返回与SQL Server 实例建立的连接有关的信息以及每个连接的详细信息
- sys.dm_os_wait_stats :SQL Server启动以来所有等待状态的等待数和等待时间。这是个累积值。
- 注:常用查看系统等待的语句文章最后奉上
常见的等待类型
- CXPACKET : 当尝试同步查询处理器交换迭代器时出现。如果针对该等待类型的争用成为问题时,可以考虑降低并行度。
- IO_COMPLETION : 在等待 I/O 操作完成时出现。通常,该等待类型表示非数据页 I/O。
- PAGEIOLATCH_ : 在任务等待 I/O 请求中缓冲区的闩锁时发生。
- PAGELATCH_ : 在任务等待不处于 I/O 请求中的缓冲区闩锁时发生。
- LCK_ :等待闩锁时出现。
- ASYNC_NETWORK_IO : 当任务被阻止在网络之后时出现在网络写入中。验证客户端是否正在处理来自服务器的数据。
- OLEDB :当 SQL Server 调用 Microsoft SQL Native Client OLE DB 访问接口时出现。该等待类型不用于同步。而是用于指示调用 OLE DB 访问接口的持续时间
- WRITELOG :等待日志刷新完成时出现。导致日志刷新的常见操作是检查点和事务提交。
注:等待有很多种,这里主要以这八个等待为例,用普通话讲解这八个等待的意义。并结合例子看看不同的等待中能反应出你SQL SERVER 系统中的哪些问题!
CXPACKET
CXPACKET 这个等待可以简单理解成CPU相关的等待,主要发生在并行计划中。由于并行计划需要协同多个task同时工作,那么“协同”分配等等操作的时候出现的就是这个等待,另外当并行使用的多个task其中一个被阻塞,在sys.dm_exec_requests查看wait_type等待类型也会是CXPACKET。详见:sys.dm_os_waiting_tasks 引发的疑问(上)
如果 CXPACKET 在你系统中是最为严重的等待,这时候一般的表现是你的CPU很高。
解决方案:适当调整并行度。具体设置请参见: Expert 诊断优化系列------------------你的CPU高么?
一般建议系统如果超过32个CPU 那么设置成8或者4,如果系统中都是特别短小且频繁的语句建议设置成1(取消语句并行,要慎重真的符合你的场景才好)
并行开销的阀值,主要控制SQL优化器何时选用并行计划,建议默认值,此值设置的越小优化器越容易选择并行计划。
并行度的设置是针对实例级别的设置(2016中可以对单独数据库设置)
IO类
IO_COMPLETION和PAGEIOLATCH_和WRITELOG 这三个等待是最为常见的和磁盘相关的等待。他们的不同点是 IO_COMPLETION 主要针对非数据页 I/O ,如备份操作所需的磁盘交互。PAGEIOLATCH_ 是数据页相关的磁盘等待。WRITELOG 是日志相关。
如果系统中这三个等待是主要等待,说明系统磁盘存在压力或已经成为瓶颈。
这里用PAGEIOLATCH_ 为例进行说明
PAGEIOLATCH_的 官方解释:在任务等待 I/O 请求中缓冲区的闩锁时发生。闩锁请求处于“XX”模式。长时间的等待可能指示磁盘子系统出现问题。
PAGEIOLATCH_的相关等待:
PAGEIOLATCH_DT |
在任务等待 I/O 请求中缓冲区的闩锁时发生。闩锁请求处于“破坏”模式。长时间的等待可能指示磁盘子系统出现问题。 |
PAGEIOLATCH_EX |
在任务等待 I/O 请求中缓冲区的闩锁时发生。闩锁请求处于“独占”模式。长时间的等待可能指示磁盘子系统出现问题。 |
PAGEIOLATCH_KP |
在任务等待 I/O 请求中缓冲区的闩锁时发生。闩锁请求处于“保持”模式。长时间的等待可能指示磁盘子系统出现问题。 |
PAGEIOLATCH_NL |
仅供内部使用。 |
PAGEIOLATCH_SH |
在任务等待 I/O 请求中缓冲区的闩锁时发生。闩锁请求处于“共享”模式。长时间的等待可能指示磁盘子系统出现问题。 |
PAGEIOLATCH_UP |
在任务等待 I/O 请求中缓冲区的闩锁时发生。闩锁请求处于“更新”模式。长时间的等待可能指示磁盘子系统出现问题。 |
- 怎么来理解这个官方解释呢? 首先明确一点,操作系统CPU操作的任何数据都是从内存中读取的,也就是说读取数据要经过这样的一条路:
- 磁盘中 ——> 内存中 ——> 最终使用
这里的PAGEIOLATCH_ 就是发生在, 磁盘中 ——> 内存中
以读取为例:要读取的数据页不在内存中,所以就要去磁盘上读取这部分数据页,去磁盘读取数据的时候就会产生PAGEIOLATCH_的相关等待,如果磁盘压力大,长时间不能反回数据,那么PAGEIOLATCH_的时间也会越长,语句执行的时间也会越长。
注 : 当你的系统出现大量的 PAGEIOLATCH_ 类等待,说明你磁盘可能存在压力(磁盘速度不能满足当前业务需求)或你的内存不够用,不能缓存业务常用数据而经常要与磁盘交互!
磁盘与内存压力的判断,请参见:Expert 诊断优化系列------------------冤枉磁盘了
WRITELOG 和磁盘有关的另一个等待状态,正在等待写日志记录,意味着写入速度也明显跟不上。而速度跟不上一般有两种情况:磁盘压力大响应时间长或真的速度不能满足读写需要。
PAGELATCH_
PAGELATCH_和 上面讲述的PAGEIOLATCH_ 看似很像,但中间少了 IO 这个关键。
- 磁盘中 ——> 内存中 ——> 最终使用
磁盘中——>内存中 的等待为PAGEIOLATCH_ 而 内存中——> 最终使用 的等待为 PAGELATCH_
当数据已经在内存中的时候SQL SERVER 想要使用这个数据页就要给这个数据页加锁,sql server中PAGELATCH_x和PAGEIOLATCH_x解析
- SQL Server没有明显的内存和磁盘瓶颈(恭喜你!)。
- 应用程序发来大量的并发语句在修改同一张表格里的记录,而表格架构设计以及用户业务逻辑使得这些修改都集中在同一个页面,或者数量不多的几个页面上。这些页面有的时候也被称为Hot Page。这样的瓶颈通常只会发生在并发用户比较多的、典型的OLTP系统上。
- 这种瓶颈是无法通过提高硬件配置解决的,只有通过修改表格设计或者业务逻辑,让修改分散到尽可能多的页面上,才能提高并发性能。
高能预警 : 网上很多人介绍过 PAGELATCH_ 等待,但是很少人有提及TempDB造成的 PAGELATCH_(其实也是一种Hot Page),这里简单的看一个例子:
系统中存在大量的 PAGELATCH_UP等待那么是什么成为了Hot Page 呢?为什么说和TempDB有关呢?
等待资源 “2:X:X: ”开头是TempDB,系统中存在大量且高并发的语句使用临时表和表变量,所以引起TEMPDB瓶颈。TempDB的诊断和优化请关注后续文章。
LCK_
上面说的PAGELATCH_和PAGEIOLATCH_LCK_这种就真真的"锁" 了!LCK_类型中的所有很多,如果这种等待在系统中大量存在,可以说明,系统语句间的相互阻塞严重。如大家都知道的当你update一张表的时候,你的select会被阻塞直到update完成。这里就不过多介绍场景了,主要看一下解决此类等待的主要方法:
- 语句优化,让语句执行的更快,减少等待时间。
- 采用批量操作代替循环方式。
- 尽量减少事务的长度。
- 上述都不能缓解...请选用读写分离。
LCK_类型中包含:(这里不做详细解读了)
LCK_M_RIn_NL |
当某任务正在等待获取当前键值上的 NULL 锁以及当前键和上一个键之间的插入范围锁时出现。键上的 NULL 锁是指立即释放的锁。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_RIn_S |
当某任务正在等待获取当前键值上的共享锁以及当前键和上一个键之间的插入范围锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_RIn_U |
任务正在等待获取当前键值上的更新锁以及当前键和上一个键之间的插入范围锁。有关锁兼容性矩阵,请参阅sys.dm_tran_locks。 |
LCK_M_RIn_X |
当某任务正在等待获取当前键值上的排他锁以及当前键和上一个键之间的插入范围锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_RS_S |
当某任务正在等待获取当前键值上的共享锁以及当前键和上一个键之间的共享范围锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_RS_U |
当某任务正在等待获取当前键值上的更新锁以及当前键和上一个键之间的更新范围锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_RX_S |
当某任务正在等待获取当前键值上的共享锁以及当前键和上一个键之间的排他范围锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_RX_U |
当某任务正在等待获取当前键值上的更新锁以及当前键和上一个键之间的排他范围锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_RX_X |
当某任务正在等待获取当前键值上的排他锁以及当前键和上一个键之间的排他范围锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_S |
当某任务正在等待获取共享锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_SCH_M |
当某任务正在等待获取架构修改锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_SCH_S |
当某任务正在等待获取架构共享锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_SIU |
当某任务正在等待获取共享意向更新锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_SIX |
当某任务正在等待获取共享意向排他锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_U |
当某任务正在等待获取更新锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_UIX |
当某任务正在等待获取更新意向排他锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
LCK_M_X |
当某任务正在等待获取排他锁时出现。有关锁兼容性矩阵,请参阅 sys.dm_tran_locks。 |
ASYNC_NETWORK_IO
此等待状态出现在SQLServer已经把数据准备好,但是网络没有足够的发送速度跟上,所以SQLServer的数据没地方存放。
- 出现这种情况一般不是数据库的问题,调整数据库配置不会有大的帮助。
- 网络层的瓶颈当然是一个可能的原因:对此要考虑是否真有必要返回那么多数据?
- 应用程序端的性能问题,也会导致SQLServer里的ASYNC_NETWORK_IO等待。如果见到了这个类型的等待,就要检查应用程序的健康状况,也要检查应用是否有必要想SQLServer申请这么大的结果集。
- 程序返回结果集的方式 : 详见 select * from table 时间长
--------------博客地址---------------------------------------------------------------------------------------
Expert 诊断优化系列 http://www.cnblogs.com/double-K/
-----------------------------------------------------------------------------------------------------
总结:系统等待往往能直接反应出系统问题。本文主要介绍了 CXPACKET —— CPU,PAGEIOLATCH_ —— 磁盘、内存的联系。
等待是系统中不能避免的,但通过语句优化,结构设计优化都能缓解这些阻塞。
语句的慢和等待有着密不可分的联系。
出现CXPACKET 一般考虑降低并行度,PAGEIOLATCH_ 一般考虑内存和磁盘(一般情况语句优化可以解决),WRITELOG 一般意味着写入速度跟不上(如果程序对磁盘的冲击已经降到最小还是跟不上,那么才意味着你需要更好的硬件了)。
-------------------------干货到了--------------------------------------------------------------------------
运行语句监控(非常好用哦~)
WITH sess AS
(
SELECT
es.session_id,
database_name = DB_NAME(er.database_id),
er.cpu_time,
er.reads,
er.writes,
er.logical_reads,
login_name,
er.status,
blocking_session_id,
wait_type,
wait_resource,
wait_time,
individual_query = SUBSTRING (qt.text, (er.statement_start_offset/2)+1, ((CASE WHEN er.statement_end_offset = -1 THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2 ELSE er.statement_end_offset END - er.statement_start_offset)/2)+1),
parent_query = qt.text,
program_name,
host_name,
nt_domain,
start_time,
DATEDIFF(MS,er.start_time,GETDATE()) as duration,
(SELECT query_plan FROM sys.dm_exec_query_plan(er.plan_handle)) AS query_plan
FROM
sys.dm_exec_requests er
INNER JOIN sys.dm_exec_sessions es ON er.session_id = es.session_id
CROSS APPLY sys.dm_exec_sql_text(er.sql_handle)as qt
WHERE
es.session_id > 50
AND es.session_Id NOT IN (@@SPID)
)
SELECT
*
FROM
sess
UNION ALL SELECT
es.session_id,
database_name = '',
0,
0,
0,
0,
login_name,
es.status,
0,
'',
'',
'',
qt.text,
parent_query = qt.text,
program_name,
host_name,
nt_domain,
es.last_request_start_time,
DATEDIFF(MS,es.last_request_start_time,GETDATE()) as duration,
NULL AS query_plan
FROM
sys.dm_exec_sessions es
INNER JOIN sys.dm_exec_connections ec ON es.session_id = ec.session_id
CROSS APPLY sys.dm_exec_sql_text(ec.most_recent_sql_handle)as qt
WHERE
ec.most_recent_session_id IN
(
SELECT blocking_session_id FROM sess WHERE blocking_session_id NOT IN(SELECT DISTINCT session_id FROM sess)
)
ORDER BY
1, 2
----------------------------------------------------------------------------------------------------
注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接!
若您觉得这篇文章还不错请点击下右下角的推荐,非常感谢!
引用高大侠的一句话 :“拒绝SQL Server背锅,从我做起!”
为了方便阅读给出系列文章的导读链接: