DBA的神兵利器——DMVs and DMFs

在SQL Server 2000的時候,我們要想做Performance Tuning的時候,多半都必須用到Profiler或者SQL Tracer來跟蹤,這東西一是結果分析起來麻煩,二是對系統資源消耗太大。在SQL Server 2005中,提供了動態管理視圖和函數(Dynamic Management Views and Dynamic Management Functions),大大的方便了我們對系統運行情況的監控,故障診斷和性能優化。現在除了Debug以外,已經很少再對生產系統使用Profiler。順便說一下,SQL Server 2008有一個更好,更強大的Data Collector,可以收集系統信息放入數據倉庫,再進行分析的,那時候DBA就更方便了。

下面我會Step by step的介紹,如何使用DMV和DMF來診斷系統情況,介紹到的全部知識都來自於聯機叢書(Books Online)。

 

Issue:在新上線一個系統後,我發現數據庫服務器的CPU有所升高,達到20-30%,Peak time的時候甚至會達到50%。於是我打開性能監視器,發現SQL Logins/sec平均超過2000。那麼,我希望知道,是哪些SQL語句調用次數如此頻繁,找到了這些SQL語句之後,就可以進行有針對的優化。

Step1:首先我們看看有什麼DMV是適用的。在聯機叢書的索引中輸入動態管理視圖 [SQL Server],發現下面有個鏈接與執行有關的動態管理視圖和函數,這裏面有一個視圖叫sys.dm_exec_query_stats,顧名思義,應該是對執行查詢的統計信息。

Step2:看看sys.dm_exec_query_stats的描述和字段,這個對象是返回緩存查詢計劃的聚合性能統計信息。包含的字段有execution_count,表示計劃自上次編譯以來所執行的次數。OK,就是它了。

Step3:怎麼用這個視圖呢?那些列好複雜,一時沒有頭緒,往下找找看。有了!下面有個示例,按平均 CLR 時間返回有關前五個查詢的信息。

SELECT TOP 5 creation_time, last_execution_time, total_clr_time,

    total_clr_time/execution_count AS [Avg CLR Time], last_clr_time,

    execution_count,

    SUBSTRING(st.text, (qs.statement_start_offset/2) + 1,

    ((CASE statement_end_offset

        WHEN -1 THEN DATALENGTH(st.text)

        ELSE qs.statement_end_offset END

            - qs.statement_start_offset)/2) + 1) as statement_text

FROM sys.dm_exec_query_stats as qs

CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as st

ORDER BY total_clr_time/execution_count DESC;

GO

 

寫不出來我總會改吧,照葫蘆畫瓢可輕鬆多了。

Step4: 我要做的是把排序條件的平均CLR時間改成平均每秒執行次數。看了一下字段列表,發現3個字段是有用的。creation_time 編譯計劃的時間。last_execution_time  上次執行計劃的時間。execution_count 計劃自上次編譯以來所執行的次數。我考慮用(last_execution_time - creation_time ) /execution_count來得到平均每秒執行次數。修改後的SQL語句如下

SELECT TOP 5 creation_time, last_execution_time, 

execution_count / datediff(second, creation_time, last_execution_time) AS [Execute Count Per Second],

    execution_count,

    SUBSTRING(st.text, (qs.statement_start_offset/2) + 1,

    ((CASE statement_end_offset

        WHEN -1 THEN DATALENGTH(st.text)

        ELSE qs.statement_end_offset END

            - qs.statement_start_offset)/2) + 1) as statement_text

FROM sys.dm_exec_query_stats as qs

CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as st

ORDER BY  execution_count / datediff(second, creation_time, last_execution_time) DESC;

 

Step5:執行後發現有點小問題,出現錯誤信息

Msg 8134, Level 16, State 1, Line 1
Divide by zero error encountered.

錯誤信息說被零除,那麼是有datediff(second, creation_time, last_execution_time)等於0的。OK,我們加點條件,標準的做法是判斷datediff(second, creation_time, last_execution_time)

>0,我判斷execution_count > 100,在這個地方也能夠達到同樣的效果。修改的結果如下:

SELECT TOP 5 creation_time, last_execution_time, 

execution_count / datediff(second, creation_time, last_execution_time) AS [Execute Count Per Second],

    execution_count,

    SUBSTRING(st.text, (qs.statement_start_offset/2) + 1,

    ((CASE statement_end_offset

        WHEN -1 THEN DATALENGTH(st.text)

        ELSE qs.statement_end_offset END

            - qs.statement_start_offset)/2) + 1) as statement_text

FROM sys.dm_exec_query_stats as qs

CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as st

WHERE execution_count > 100

ORDER BY  execution_count / datediff(second, creation_time, last_execution_time) DESC;

Step 6:再調整一下所需的字段,我對creation_time, last_execution_time其實沒啥興趣,我更關心這個SQL語句執行的時間,I/O方面的信息,看了下字段列表,有total_physical_reads 此計劃自編譯後在執行期間所執行的物理讀取總次數,total_elapsed_time 完成此計劃的執行所佔用的總時間(微秒)。再除以execution_count就是平均值了。修改後的SQL語句如下:

SELECT     TOP 100 execution_count / datediff(second, creation_time, last_execution_time) AS [Execute Times Per Second], execution_count,

total_logical_reads  /execution_count AS [Avg Logical Reads],

total_elapsed_time /execution_count AS [Avg Elapsed Time],

SUBSTRING(st.text, (qs.statement_start_offset / 2) + 1, ((CASE statement_end_offset WHEN - 1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2) + 1) AS statement_text

FROM         sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st

WHERE     execution_count > 100

ORDER BY 1 DESC;

 

Step7:OK,差不多能滿足我的需求了,但是美中不足的是,我們對數據庫的訪問全是通過存儲過程來實現的,要找到這段SQL語句出自哪個存儲過程,雖然不是很麻煩,但是還是要動點手腳的,要是能在這個查詢中顯示出來就好了。 這個查詢中用到了sys.dm_exec_sql_text,看看這裏面有啥內容。在聯機叢書的索引中輸入sys.dm_exec_sql_text。Bingo!對於諸如存儲過程、觸發器或函數之類的數據庫對象,SQL 句柄派生自數據庫 ID、對象 ID 和對象編號。太好了,要的就是這個,有了ID就可以轉換成名字了,object_name函數是早已熟悉的。最終的結果如下

SELECT     TOP 100 execution_count / datediff(second, creation_time, last_execution_time) AS [Execute Count Per Second], execution_count,

total_logical_reads  /execution_count AS [Avg Logical Reads],

total_elapsed_time /execution_count AS [Avg Elapsed Time],

db_name(st.dbid) as [database name],

object_name(st.objectid, st.dbid) as [object name],

SUBSTRING(st.text, (qs.statement_start_offset / 2) + 1, ((CASE statement_end_offset WHEN - 1 THEN DATALENGTH(st.text) ELSE qs.statement_end_offset END - qs.statement_start_offset) / 2) + 1) AS statement_text

FROM         sys.dm_exec_query_stats AS qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st

WHERE     execution_count > 100

ORDER BY 1 DESC;

在實際應用的時候,再根據情況稍做修改即可。希望各位讀者從這篇文章學到的不僅是sys.dm_exec_query_stats ,更重要的是瞭解到使用Books Online的方法。

謝謝閱讀!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章