SQL Server 性能調優(方法論)【轉】

目錄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的壓力形成的。若是單純的添加物理設備,可是沒有找到根本緣由,當數據量增加後,依然會出現相同的問題。

 

waitevent的基本troubleshooting

 

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 信息相減。



虛擬文件信息(virtual file Statistics)

一般,當使用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 若是看不懂的或者想更深刻了解的,能夠看原文。

相關文章
相關標籤/搜索