自来云

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::
接着上一章: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

 

posted on 2018-05-30 10:21  自来云  阅读(1218)  评论(0编辑  收藏  举报