zoukankan      html  css  js  c++  java
  • SQL Server 2008性能故障排查(三)——IO

    接着上一章:CPU瓶颈
    I/O瓶颈(I/O Bottlenecks):
    SQLServer的性能严重依赖I/O子系统。除非你的数据库完全加载到物理内存中,否则SQLServer会不断地把数据库文件从缓存池中搬进搬出,这会引起大量的I/O传输。同样地,日志记录在事务被声明为已提交前必须写入磁盘。最后,SQLServer基于许多原因使用tempdb,比如存储临时结果、排序和保持行版本。所以一个好的I/O子系统是SQLServer性能关键。
    除非数据文件包括tempdb需要回滚事务,否则日志文件是顺序访问的。而数据文件和tempdb是随机访问的。所以作为常规规则,你应该把日志文件与数据文件分离到独立的磁盘中。本文不是关注于如何配置你的I/O设备,但关注于如何识别你的系统是否有I/O瓶颈。在I/O瓶颈被识别之后,你应该重新配置你的I/O子系统。
    如果你的I/O子系统很慢,你的用户将体验得到性能问题,响应时间过慢和因为超时而导致任务失败。
    可以使用以下的性能计数器去识别I/O瓶颈。但是要注意,如果你的收集间隔过短,那么平均值会趋向倾斜于低值那段。比如,很难说明为什么I/O会每60秒涨跌。同时,你也不能仅仅根据一个计数器的值来确定是否有瓶颈。需要通过多个值来反复验证你的想法:
    PhysicalDisk Object:Avg.Disk Queue:物理读写请求锁等待的平均队列值。当该值长期超过2时,你的系统可能存在I/O瓶颈了。
    Avg.Disk Sec/Read:是一个平均秒数,是每秒从磁盘上读取数据的次数,下面是值及其代表意思:
    • 小于10ms ——非常好
    • 10~20ms——OK
    • 20~50ms——慢,需要重视
    • 大于50ms——严重的I/O瓶颈。
    Avg.Disk Sec/Write:与Avg.Disk Sec/Read相对应。
    Physical Disk:%Disk Time:是针对被选定的磁盘忙于读写请求所运行时间的百分数。一般的指标线是大于50%就意味着有I/O瓶颈。
    Avg.Disk Reads/Sec:是读操作在磁盘上的频率。确保这个频率低于磁盘极限的85%,当超过了85%后,访问时间就会以指数速度增加。
    Avg.Disk Writes/Sec:于Avg.Disk Reads/Sec相对应。
    当你使用这些计数器时,你需要对于RAID作出调整,可以使用以下公式:
     Raid 0 -- I/Os per disk = (reads + writes) / number of disks
     Raid 1 -- I/Os per disk = [reads + (2 * writes)] / 2
     Raid 5 -- I/Os per disk = [reads + (4 * writes)] / number of disks
     Raid 10 -- I/Os per disk = [reads + (2 * writes)] / number of disks
    比如,如果你有一个RAID-1,使用两个物理磁盘,计数器值为:
    Disk Reads/sec 80
    Disk Writes/sec 70
    Avg.Disk Queue length 5
    这样通过公式计算:(80 + (2 * 70))/2 = 110 I/Os 每个磁盘,而你的磁盘等待队列长度等于5/2=2.5。意味着已经到达了I/O瓶颈边界。
    你也可以检查lacth等待来识别I/O瓶颈。这种等待说明当一些页面用于读或者写访问时,同时这些页面在缓冲池中不可用(或者不存在)而造成的物理I/O等待。当页面在缓冲池中找不到时。就会产生一个异步的I/O,然后检查这个I/O的状态。当I/O状态已经被标注为已完成时,此时工作负载趋于平衡。否则,将会等待PAGEIOLATCH_EX 或者PAGEIOLATCH_SH,这根据请求类型而定。可以使用一下DMV来发现I/O闩锁的等待统计信息:
    
    [sql] view plain copy
     print?
    1.    Select  wait_type,   
    2.            waiting_tasks_count,   
    3.            wait_time_ms  
    4.    from    sys.dm_os_wait_stats    
    5.    where   wait_type like 'PAGEIOLATCH%'    
    6.    order by wait_type  
    
    
    
    下面是结果的例子:
    wait_type       waiting_tasks_count  wait_time_ms   signal_wait_time_ms
    -----------------------------------------------------------------------
    PAGEIOLATCH_DT  0                    0                    0
    PAGEIOLATCH_EX  1230                 791                  11
    PAGEIOLATCH_KP  0                    0                    0
    PAGEIOLATCH_NL  0                    0                    0
    PAGEIOLATCH_SH  13756                7241                 180
    PAGEIOLATCH_UP  80                   66                   0
    当I/O完成时,工作线程将被至于可运行队列。I/O完成到工作线程确实被排程的时间在signal_wait_time_ms列中可以看到,如果你的waiting_task_counts和wait_time_ms有偏离常值,证明有I/O问题。对于这种情况,有必要在SQLServer运行正常时,建立性能基线和关键的DMV查询输出。这些等待类型能显示出你的I/O子系统是否有严重的瓶颈。但它们不提供任何可见的物理磁盘问题
    你可以通过下面的DMV查询来找到目前正在挂起的I/O请求。你可以定期执行下面语句来检查I/O子系统的健康情况和隔离那些有I/O瓶颈的物理磁盘:
    [sql] view plain copy
     print?
    1.    select   
    2.        database_id,   
    3.        file_id,   
    4.        io_stall,  
    5.        io_pending_ms_ticks,  
    6.        scheduler_address   
    7.    from    sys.dm_io_virtual_file_stats(NULL, NULL)t1,  
    8.            sys.dm_io_pending_io_requests as t2  
    9.    where   t1.file_handle = t2.io_handle  
    
    
    
    
    下面是一个输出例子,是对特定的数据库输出,在运行查询的时刻,有3个被挂起的I/O请求。你可以使用database_id 和file_id列来查找文件所映射的物理磁盘。Io_pending_ms_ticks值表示单个I/O在挂起队列中等待的总时间。
    
    
    Database_id File_Id io_stallio_pending_ms_ticksscheduler_address
    -------------------------------------------------------------
    6 1 10804780x0227A040
    6 1 10804780x0227A040
    6 2 101451310x02720040
    
    
    
    
    解决方案:
    当你发现有I/O瓶颈时,你第一本能反应可能是升级I/O子系统,以应对目前的工作负载。这种方式当然有效,但是在此之前,你要考虑在硬件投入上的开销,要检查I/O瓶颈是否因为不正确的配置和/或查询计划导致的。我们建议你根据以下步骤去检查:
    1、 配置(Configuration):检查SQLServer的内存配置。如果SQLServer配置中存在内存不足的问题,这会引起更多I/O开销。你可以检查下面的计数器来识别是否存在内存压力:
     Buffer Cache hit ratio
     Page Life Expectancy
     Checkpoint Pages/sec
     Lazywrites/sec
    关于内存压力将在内存篇详细说明
    2、 查询计划:检查执行计划和识别哪步导致了更多的I/O消耗。尽可能选择更好的方法比如索引来最小化I/O。如果存在丢失索引,可以使用DTA来找到。
    下面的DMV查询可以用于发现批处理或者请求产生最多的I/O的查询。注意这里不统计物理写,如果你懂得数据库是如何运作的,就会知道为什么。在同一个请求中DML和DDL语句,不是直接把数据页写入磁盘,而只有已经提交的事务才会被写入磁盘。通常物理写只在checkpoint或者lazywriter发生时才出现。可以使用下面的DMV来查找产生最多I/O的5个查询,优化这些查询以便实现更少的逻辑读,并进一步缓解缓存池的压力。这样你能提高其他请求在缓存池中直接找到数据的机会(特别在重复执行时),从而替代物理I/O的性能。因此,这个系统的性能都能得到改进。
    下面是通过hash join来做两表关联的例子:
    [sql] view plain copy
     print?
    1.    create table t1 (c1 int primary key, c2 int, c3 char(8000))  
    2.       create table t2  (C4 int, c5 char(8000))  
    3.    go  
    4.      
    5.      
    6.       --load the data  
    7.    declare @i int  
    8.    select @i = 0  
    9.    while (@i < 6000)   
    10.    begin  
    11.        insert into t1 values (@i, @i + 1000, 'hello')  
    12.       insert into t2 values (@i,'there')  
    13.       set @i = @i + 1  
    14.    end  
    15.    --now run the following query  
    16.    select c1, c5  
    17.    from t1 INNER HASH JOIN t2 ON t1.c1 = t2.c4  
    18.    order by c2   
    19.      
    20.      
    21.    Run another query so that there are two queries to look at for I/O stats  
    22.      
    23.      
    24.    select SUM(c1) from t1  
    
    
    这两个查询在一个批处理中运行,接下来。使用下面的DMV来检查查询引起的I/O:
    
    
    [sql] view plain copy
     print?
    1.    SELECT TOP 5   
    2.        (total_logical_reads/execution_count) AS avg_logical_reads,  
    3.        (total_logical_writes/execution_count) AS avg_logical_writes,  
    4.        (total_physical_reads/execution_count) AS avg_phys_reads,  
    5.        execution_count,   
    6.        statement_start_offset as stmt_start_offset,   
    7.        (SELECT SUBSTRING(text, statement_start_offset/2 + 1,  
    8.            (CASE WHEN statement_end_offset = -1   
    9.                THEN LEN(CONVERT(nvarchar(MAX),text)) * 2   
    10.                    ELSE statement_end_offset   
    11.                END - statement_start_offset)/2)  
    12.         FROM sys.dm_exec_sql_text(sql_handle)) AS query_text,   
    13.          (SELECT query_plan from sys.dm_exec_query_plan(plan_handle)) as query_plan  
    14.    FROM sys.dm_exec_query_stats    
    15.    ORDER BY (total_logical_reads + total_logical_writes)/execution_count DESC  
    
    
    
    
    你当然可以通过改变查询语句来获得不同的数据显示。比如你可以按(total_logical_reads + total_logical_writes)/execution_count 来排序。作为选择,你可能想去按物理I/O来排序等,但是,逻辑读写书对判断是否有I/O问题是很有用的。输出类似这样:
    avg_logical_reads    avg_logical_writes   avg_phys_reads       
    -----------------    ------------------   ---------------
    16639 10    1098
    6023 0    0
    execution_count      stmt_start_offset
    ---------------      -----------------
    1 0
    1 154
    
    
    Query_text      Query_plan                        
    -----------------------------------          -----------
    select c1, c5  from t1 INNER HASH JOIN<link to query plan>
    select SUM(c1) from t1                       <link to query plan>
    
    
    这些输出告诉你一些重要的信息,第一,显示最多的I/O。你也可以通过SQL Text列来查看是否可以通过重写语句来降低I/O。验证这些执行计划是否已经最佳的。比如,一个新的索引可能有帮助。第二、第二个批处理不引起任何物理I/O因为所有需要的表的页面已经缓存到缓冲区。第三、执行次数能用于识别是否它是一个一次性查询或者它是否频繁执行,因此需要对此详细考量。
    3、 数据压缩:从2008开始,你能使用数据压缩来降低表和索引的体积。压缩程度完全取决于架构和数据分布。一般情况下,可以达到50~60%的压缩率。一些特殊情况下可以达到90%。意味着当你能压缩到50%时,你已经比较有效地降低了I/O。数据压缩会引起CPU增加。这里有一些策略:
    为什么不把整个数据库压缩?对此,给出一个极端的例子:如果你有一个大表,叫做T,有10页,而整个数据库有1000万页。压缩T没有多大好处。即使SQLServer能把10页压缩到1页,你努力减少数据库的大小,但你可能会造成CPU的负担增加。在现实的工作负载中,不能很明显地作出选择。但是这个例子只是你在压缩前要考虑的情况而已。我们的建议是:在你压缩一个对象之前,使用sp_estimate_data_compression_savings存储过程来评估它的大小、利用情况和预估压缩等信息。注意以下信息:
     对象的大小是否比数据库总体大小小很多,这样的情况不会给你带来太多好处。
     如果对象经常被用于DML或者SELECT操作,你将面临比较大的CPU消耗。特别是在CPU已经存在瓶颈时,你可以使用sys.dm_db_index_operational_stats去发现对象使用情况来判断表、索引、分区等等的命中情况。
     压缩预留情况是基于架构和基于数据的。实际上,一些对象,压缩后可能会更大。或者节省的空间会微不足道。
    如果你有一个分区表,且某些分区的数据不经常访问。你可以使用页压缩来压缩分区和重组索引。这适用在不长使用的分区上。相信信息可以看:(http://blogs.msdn.com/sqlserverstorageengine/archive/tags/Data+Compression/default.aspx)
    4、 升级I/O子系统:如果你确保SQLServer的配置合理,并且检查执行计划后仍然存在I/O瓶颈,最后的选择就只能升级I/O带宽了:
     增加更多的物理驱动或者更换更快的磁盘。
     增加更快的I/O控制器。
    
    
    下一章:tempdb
  • 相关阅读:
    Lightoj 1082【RMQ】
    hrbust1444 逃脱 【BFS】
    萌新学习笔记之哈夫曼树
    lightoj 1085【离散化+树状数组】
    CodeForces 586D【BFS】
    lightoj 1089 【离散化+线段树】
    lightoj 1088【树状数组+离散化】
    《算法导论》笔记 第6章 6.2保持堆的性质
    《算法导论》笔记 第6章 6.1堆
    【python】__all__
  • 原文地址:https://www.cnblogs.com/binghou/p/9109452.html
Copyright © 2011-2022 走看看