目錄sql
肯定思路數據庫
wait event的基本troubleshootingwindows
虛擬文件信息(virtual file Statistics)緩存
性能指標服務器
執行計劃緩衝的使用網絡
總結session
性能調優很難有一個固定的理論。調優原本就是處理一些特殊的性能問題。併發
一般一旦拿到一個服務器那麼就先作一下性能檢查。查看整個數據庫是運行在什麼樣的情況下的。ide
分析收集的數據想像這種狀況是否合理。工具
一個數據庫操做的時間都是執行時間+等待時間,在沒法估計執行時間的時候看要看看等待時間。
那麼等待時間分爲鎖等待時間和資源等待時間。
那麼就先用 sys.dm_os_wait_stats動態性能視圖,查看主要的情況。若是pageiolatch_sh等待很大,那麼就說明,session在等待buffer pool的頁。當一個session要select一些數據,可是剛恰好,這些數據並無在buffer pool 中,那麼sql server 就會分配一些緩存這些緩存是屬於buffer pool 的,用來存放從磁盤讀取出來的數據,在讀取的時候都會給這些緩存上latch(能夠當作是鎖)。當存在io瓶頸的時候,那麼磁盤上的數據不能當即讀到buffer pool 中就會出現等待latch的狀況。這個多是io過慢,也有多是在作一些多餘的io形成的。
那麼接下來查看sys.dm_io_virtual_file_stats 性能視圖來肯定哪一個數據庫形成了怎麼大的延遲。而且經過physical disk \avg.disk reads/sec和physical disk\avg.disk writes/sec來肯定到底數據庫有多少io負載。
接下來經過 sys.dm_exec_query_stats 查看執行計劃,經過查看高物理讀的sql和執行計劃看看有沒有優化的空間。如添加索引,修改sql,優化引擎訪問數據的方法。
有可能,sql 語句已經不能再優化,可是性能仍是不行,每每這種sql是報表查詢類的sql,會從磁盤中讀取大量數據,不少數據每每在buffer pool 找不到那麼就會發生大量的pageiolatch_sh等待。這時,咱們就要看看是不是內存不足照成的,用perfmon 查看 page life expectancy(頁壽命長度),free list stalls/sec(等待空頁的次數)和Lazy writes/sec。 page life expectancy 波動很厲害,free liststalls/sec 一直大於0,Lazy writes/sec 的量也很大,那麼就說明buffer pool 不夠大。可是也有多是sql 寫的不嚴謹,select了不少不必的數據。
在上面的troubleshooting 過程當中,很容易走入一個誤區,sys.dm_io_virtual_file_stats和一些性能指標,就會很容易判定說io有問題,須要額外的預算來擴展io的性能,可是擴展io是比較貴的。io性能不理想頗有可能miss index或者buffer pool的壓力形成的。若是單純的添加物理設備,可是沒有找到根本緣由,當數據量增加後,依然會出現相同的問題。
wait statistics 是SQLOS跟蹤獲得的
SQLOS 是一個僞操做系統,是SQL Server 的一部分,有調度線程,內存管理等其餘操做。
SQLOS比windows調度器更好的調度sql server 線程。SQLOS的調度器間的交互,會比強佔式的系統調度又更好的併發性
當sql server 等待一個sql 執行的時候,等待的時間會被sqlos捕獲,這些時間都會存放在 sys.dm_os_wait_stats性能視圖中。各類等待時間的長度,而且和其餘的性能視圖,性能計數器結合,能夠很明顯的看出性能問題。
對於未知的性能問題sys.dm_os_wait_stats 用來判斷性能問題是很好用的,可是在服務器重啓或者dbcc 命令清空 sys.dm_os_wait_stats後會很好分析,時間一長就很難分析,由於等待時間是累計的,搞不清楚哪一個是你剛剛執行出來的時間。固然能夠考慮先捕獲一份,當sql 執行完後,再捕獲一份,進行比較。
查看wait event,獲得的信息只是實際性能問題的其中一個症狀,爲了更利用wait event 信息,你須要瞭解資源等待和非資源等待的區別,還有須要瞭解其餘troubleshooting信息。
在sql server中有一部分的sql是沒問題的,可使用一下sql 語句查看說有的 session的wait event
SELECT DISTINCT
wt.wait_type
FROM sys.dm_os_waiting_tasks AS wt
JOIN sys.dm_exec_sessions AS s ON wt.session_id = s.session_id
WHERE s.is_user_process = 0
由於很大一部分是正常的,因此提供了一個sql 來過濾正常查詢操做
SELECT TOP 10
wait_type ,
max_wait_time_mswait_time_ms ,
signal_wait_time_ms ,
wait_time_ms - signal_wait_time_ms AS resource_wait_time_ms ,
100.0 * wait_time_ms / SUM(wait_time_ms) OVER ( )
AS percent_total_waits ,
100.0 * signal_wait_time_ms / SUM(signal_wait_time_ms) OVER ( )
AS percent_total_signal_waits ,
100.0 * ( wait_time_ms - signal_wait_time_ms )
/ SUM(wait_time_ms) OVER ( ) AS percent_total_resource_waits
FROM sys.dm_os_wait_stats
WHERE wait_time_ms > 0 -- remove zerowait_time
AND wait_type NOT IN -- filter outadditional irrelevant waits
( 'SLEEP_TASK', 'BROKER_TASK_STOP', 'BROKER_TO_FLUSH',
'SQLTRACE_BUFFER_FLUSH','CLR_AUTO_EVENT', 'CLR_MANUAL_EVENT',
'LAZYWRITER_SLEEP', 'SLEEP_SYSTEMTASK', 'SLEEP_BPOOL_FLUSH',
'BROKER_EVENTHANDLER', 'XE_DISPATCHER_WAIT', 'FT_IFTSHC_MUTEX',
'CHECKPOINT_QUEUE', 'FT_IFTS_SCHEDULER_IDLE_WAIT',
'BROKER_TRANSMITTER', 'FT_IFTSHC_MUTEX', 'KSOURCE_WAKEUP',
'LAZYWRITER_SLEEP', 'LOGMGR_QUEUE', 'ONDEMAND_TASK_QUEUE',
'REQUEST_FOR_DEADLOCK_SEARCH', 'XE_TIMER_EVENT', 'BAD_PAGE_PROCESS',
'DBMIRROR_EVENTS_QUEUE', 'BROKER_RECEIVE_WAITFOR',
'PREEMPTIVE_OS_GETPROCADDRESS', 'PREEMPTIVE_OS_AUTHENTICATIONOPS',
'WAITFOR', 'DISPATCHER_QUEUE_SEMAPHORE', 'XE_DISPATCHER_JOIN',
'RESOURCE_QUEUE' )
ORDER BY wait_time_ms DESC
檢查wait event通常只關注前幾個等待信息,查看高等待時間的等待類型。
CXPACKET:
代表併發查詢的等待時間,一般不會馬上產生問題,也多是由於別的性能問題,致使CXPACKET等待太高。
SOS_SCHEDULER_YIELD
任務在執行的時候被調度器中斷,被放入可執行隊列等待被運行。這個時間過長多是cpu壓力形成的。
THREADPOOL
一個任務必須綁定到一個工做任務才能執行,threadpool 就是task等待被綁定的時間。出現threadpool太高多是,cpu不夠用,也多是大量的併發查詢。
LCK_*
這中等待類型太高,說明可能session發生堵塞,能夠看sys.dm_db_index_operational_stats 得到更深刻的內容
PAGEIOLATCH_*,IO_COMPLETION,WRITELOG
這些每每和磁盤的io瓶頸關聯,根本緣由每每都是效率極差的查詢操做消費了過多的內存。PAGEIOLATCH_*和數據庫文件的讀寫延遲相關。writelog和事務日 志文件的讀寫相關。這些等待最好和sys.dm_io_virtual_file_stats 關聯肯定問題是發生在數據庫,數據文件,磁盤仍是整個實例。
PAGELATCH_*
在buffer pool 中非io等待latch。PAGELATCH_* 大量的等待一般是分配衝突。當tempdb中大量的對象要被刪除或者建立,那麼系統就會對SGAM,GAM和PFS的分配發生衝突。
LATCH_*
LATCH_*和內部cache的保護,這種等待太高會發生大量的問題。能夠經過 sys.dm_os_latch_stats 查看詳細內容。
ASYNC_NETWORK_IO
這個等待不徹底代表網絡的瓶頸。事實上多數狀況下是客戶端程序一行一行的處理sql server 的結果集致使。發生這種問題那麼就修改客戶端代碼。
簡單的解釋了主要的等待,減小在分析wait event 的時候走的彎路。
爲了肯定是否已經排除問題能夠用DBCC SQLPERF('sys.dm_os_wait_stats', clear)清除wait event。也能夠用2個wait event 信息相減。
一般,當使用wait event 分析問題的時候,都爲認爲很想io的性能問題。可是wait event 並不能說明io是怎麼發生的,因此頗有可能會誤判
這就是爲何要使用sys.dm_os_latch_stats 查看的緣由,能夠查看累計的io統計信息,每一個文件的讀寫信息,日誌文件的讀寫,能夠計算讀寫的比例,io等待的次數,等待的時間。
SELECT DB_NAME(vfs.database_id) AS database_name ,
vfs.database_id ,
vfs.FILE_ID ,
io_stall_read_ms / NULLIF(num_of_reads, 0) AS avg_read_latency ,
io_stall_write_ms / NULLIF(num_of_writes, 0)
AS avg_write_latency ,
io_stall / NULLIF(num_of_reads + num_of_writes, 0)
AS avg_total_latency ,
num_of_bytes_read / NULLIF(num_of_reads, 0)
AS avg_bytes_per_read ,
num_of_bytes_written / NULLIF(num_of_writes, 0)
AS avg_bytes_per_write ,
vfs.io_stall ,
vfs.num_of_reads ,
vfs.num_of_bytes_read ,
vfs.io_stall_read_ms ,
vfs.num_of_writes ,
vfs.num_of_bytes_written ,
vfs.io_stall_write_ms ,
size_on_disk_bytes / 1024 / 1024. AS size_on_disk_mbytes ,
physical_name
FROM sys.dm_io_virtual_file_stats(NULL,NULL) AS vfs
JOIN sys.master_files AS mf ON vfs.database_id = mf.database_id
AND vfs.FILE_ID = mf.FILE_ID
ORDER BY avg_total_latency DESC
查看是否讀寫過大,平均延時是否太高。經過這個能夠知道是不是io的問題。
若是數據文件和日誌文件是共享磁盤隊列的,avg_total_latency 比預期的要高,那麼就有多是io的問題了
若是當前的數據庫是用來歸檔數據到比較慢的存儲中,可能會有很高的PAGEIOLATCH_*和io_stall那麼咱們就須要肯定怎麼高的等待是否屬於歸檔的線程,所以在troubleshooting的時候要注意你的服務器的類型。
若是你的磁盤讀寫比例是1:10,並且又很高的 avg_total_latency 那麼就考慮把磁盤隊列換成 raid5,爲io讀提供更多的主軸。
在最開始的troubleshooting,性能指標是很是有用的。也能夠用來驗證本身的判斷是否正確。
PLA 是一個很好的性能日誌分析工具http://pal.codeplex.com. 惋惜沒有中文版,固然能夠去codeplex 下載源代碼本身修改。這個工具內嵌了性能收集集合,也就是一般要收集的一些性能指標。也內嵌了閥值模板,能夠在性能指標收集完以後作分析。
sql server 對本身的性能指標有對應的性能視圖 sys.dm_os_performance_counters。對於性能指標有些是累計值,所以須要作2個快照,相減計算結果。
DECLARE @CounterPrefix NVARCHAR(30)
SET @CounterPrefix = CASE WHEN @@SERVICENAME = 'MSSQLSERVER'
THEN 'SQLServer:'
ELSE 'MSSQL$' + @@SERVICENAME + ':'
END ;
-- Capture thefirst counter set
SELECT CAST(1 AS INT) AS collection_instance ,
[OBJECT_NAME] ,
counter_name ,
instance_name ,
cntr_value ,
cntr_type ,
CURRENT_TIMESTAMP AS collection_time
INTO #perf_counters_init
FROM sys.dm_os_performance_counters
WHERE ( OBJECT_NAME = @CounterPrefix + 'Access Methods'
AND counter_name = 'Full Scans/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Access Methods'
AND counter_name = 'Index Searches/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Buffer Manager'
AND counter_name = 'Lazy Writes/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Buffer Manager'
AND counter_name = 'Page lifeexpectancy'
)
OR ( OBJECT_NAME = @CounterPrefix + 'General Statistics'
AND counter_name = 'Processes Blocked'
)
OR ( OBJECT_NAME = @CounterPrefix + 'General Statistics'
AND counter_name = 'User Connections'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Locks'
AND counter_name = 'Lock Waits/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Locks'
AND counter_name = 'Lock Wait Time(ms)'
)OR ( OBJECT_NAME = @CounterPrefix + 'SQL Statistics'
AND counter_name = 'SQLRe-Compilations/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Memory Manager'
AND counter_name = 'Memory GrantsPending'
)
OR ( OBJECT_NAME = @CounterPrefix + 'SQL Statistics'
AND counter_name = 'Batch Requests/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'SQL Statistics'
AND counter_name = 'SQLCompilations/sec'
)
-- Wait on Secondbetween data collection
WAITFOR DELAY '00:00:01'
-- Capture thesecond counter set
SELECT CAST(2 AS INT) AS collection_instance ,
OBJECT_NAME ,
counter_name ,
instance_name ,
cntr_value ,
cntr_type ,
CURRENT_TIMESTAMP AS collection_time
INTO #perf_counters_second
FROM sys.dm_os_performance_counters
WHERE ( OBJECT_NAME = @CounterPrefix + 'Access Methods'
AND counter_name = 'Full Scans/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Access Methods'
AND counter_name = 'Index Searches/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Buffer Manager'
AND counter_name = 'Lazy Writes/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Buffer Manager'
AND counter_name = 'Page lifeexpectancy'
)
OR ( OBJECT_NAME = @CounterPrefix + 'General Statistics'
AND counter_name = 'Processes Blocked'
)
OR ( OBJECT_NAME = @CounterPrefix + 'General Statistics'
AND counter_name = 'User Connections'
)OR ( OBJECT_NAME = @CounterPrefix + 'Locks'
AND counter_name = 'Lock Waits/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Locks'
AND counter_name = 'Lock Wait Time(ms)'
)
OR ( OBJECT_NAME = @CounterPrefix + 'SQL Statistics'
AND counter_name = 'SQLRe-Compilations/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'Memory Manager'
AND counter_name = 'Memory GrantsPending'
)
OR ( OBJECT_NAME = @CounterPrefix + 'SQL Statistics'
AND counter_name = 'Batch Requests/sec'
)
OR ( OBJECT_NAME = @CounterPrefix + 'SQL Statistics'
AND counter_name = 'SQLCompilations/sec'
)
-- Calculate thecumulative counter values
SELECT i.OBJECT_NAME ,
i.counter_name ,
i.instance_name ,
CASE WHEN i.cntr_type = 272696576
THEN s.cntr_value - i.cntr_value
WHEN i.cntr_type = 65792 THEN s.cntr_value
END AS cntr_value
FROM #perf_counters_init AS i
JOIN #perf_counters_second AS s
ON i.collection_instance + 1 = s.collection_instance
AND i.OBJECT_NAME = s.OBJECT_NAME
AND i.counter_name = s.counter_name
AND i.instance_name = s.instance_name
ORDER BY OBJECT_NAME
-- Cleanup tables
DROP TABLE #perf_counters_init
DROP TABLE #perf_counters_second
主要收集一下性能指標:
• SQLServer:AccessMethods\Full Scans/sec
• SQLServer:AccessMethods\Index Searches/sec
• SQLServer:BufferManager\Lazy Writes/sec
• SQLServer:BufferManager\Page life expectancy
• SQLServer:BufferManager\Free list stalls/sec
• SQLServer:GeneralStatistics\Processes Blocked
• SQLServer:GeneralStatistics\User Connections
• SQLServer:Locks\LockWaits/sec
• SQLServer:Locks\LockWait Time (ms)
• SQLServer:MemoryManager\Memory Grants Pending
• SQLServer:SQLStatistics\Batch Requests/sec
• SQLServer:SQLStatistics\SQL Compilations/sec
• SQLServer:SQLStatistics\SQL Re-Compilations/sec
這裏又2個 Access Methods 性能指標,說明了訪問數據庫不一樣的方式,full scans/sec 表示了發生在數據庫中索引和表掃描的次數。
若是io出現瓶頸,而且伴隨着大量的掃描出現,那麼頗有可能就是miss index 或者sql 代碼不理想照成的。那麼多少次數到多少時能夠認爲有問題呢?在一般情況下 index searches/sec 比 full scans/sec 高800-1000,若是 full sacans/sec太高,那麼頗有多是miss index 和多餘的io操做引發的。
Buffer Manager 和 memory manager 一般用來檢測是否存在內存壓力,lazy writes/sec,page life expectancy ,free list stalls/sec 用來佐證是否處於內存壓力。
不少網上的文章和論壇都說,若是Page Life expectancy 低於300秒的時候,存在內存壓力。可是這只是對於之前只有4g內存的服務器的,如今的服務器通常都是32g以上內存5分鐘的閥值已經不能在說明問題了。300秒雖然已經再也不適用,可是咱們能夠用300來做爲基值來計算當前的PLE的閥值(32/4)*300 = 2400那麼若是是32g的服務器設置爲2400可能會比較合適。
若是PEL一直低於閥值,而且 lazy writes/sec一直很高,那麼有多是buffer pool壓力形成的。若是這個時候full scans/sec值也很高,那麼請先檢查是否是miss index 或者讀取了多餘的數據。
generalstatistics\processes blocked,locks\lock waits/sec和locks\lock wait time(ms)若是這3個值都是非0那麼數據庫會發生堵塞。
SQL Statistics 計數器說明了sql 的編譯或者重編譯的速度,sql compilations/sec和 batch requests/sec 成正比,那麼頗有可能大量sql 訪問都是 ad hoc方式沒法經過執行計劃緩衝優化它們,若是 SQLRe-compilations/sec 和 batch requests/sec 成正比,那麼應用程序中可能又強制從新編譯的選項。
memorymanager\momory grants pending 表示等待受權內存的等待,若是這個值很高那麼增長內存可能會有效果。可是也有多是大的排序,hash操做也可能形成,可使用調整索引或者查詢來減少這種情況。
執行計劃緩衝是sql server 的內部組件,可使用 sys.dm_exec_query_stats 查詢,下面有個sql查詢物理讀前十的計劃
SELECT TOP 10
execution_count ,
statement_start_offset AS stmt_start_offset ,
sql_handle ,
plan_handle ,
total_logical_reads / execution_count AS avg_logical_reads ,
total_logical_writes / execution_count AS avg_logical_writes ,
total_physical_reads / execution_count AS avg_physical_reads ,
t.text
FROM sys.dm_exec_query_stats AS s
CROSS APPLY sys.dm_exec_sql_text(s.sql_handle) AS t
ORDER BY avg_physical_reads DESC
在執行計劃裏面的這些值能夠看出哪些查詢物理io操做很頻繁,也能夠和wait event 和虛擬文件結合分析有問題的io操做。
咱們也可使用sys.dm_exec_query_plan()查看存在內存裏面的執行計劃。
這裏又2本書深刻的講述了查詢執行計劃:《SQL Server 2008 Query performance tuningdistilled》,《Inside Microsoft SQL Server 2008:T-SQLQuerying》。
sys.dm_exec_query_stats還用來查詢 cpu時間,最長執行時間,或者最頻繁的sql
在sql server 2008中加入了2個額外的列,query_hash,query_plan_hash用來聚合類似的sql的。對於ad hoc 過大的服務器能夠用來分析類似的sql,不一樣的編譯的總數。
上面各個部分都講了一個思惟,一個思路。要想性能調優調的好,那麼就先系統體系結構,你要清楚如前面說的miss index 一旦發生,那麼不知會影響io,還會影響內存和cpu。接下來要會分析,從一開始的簡單的性能統計信息,往下分析,用其餘統計信息排除問題,獲得性能問題的真正緣由。
文章來源於:Troubleshooting SQL Server: A Guide for theAccidental DBA 若是看不懂的或者想更深刻了解的,能夠看原文。