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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM