本文屬於Azure SQL DB/DW系列
上一文:Azure SQL DB/DW 系列(13)——使用Query Store(2)——報表介紹(2)
本文繼續如何使用Query Store的常用場景
前言
Query Store有很多用途,基於它的收集功能,很適合作爲服務器的性能基線。性能基線是一個標準,用來後續判斷服務器是否存在性能問題。所以專業的數據庫及服務器運維,都應該制定合理的性能基線。
在啓用了Query Store之後,可以收集一個時間區間的數據,這個區間根據INTERVAL_LENGTH_MINUTES而定。可以是1,5,10,15,60和1440分鐘。
常用於做性能基線的報表是【總體資源消耗】:
通過這個報表,可以看到這個時間區間內,服務器及數據庫的資源使用情況。比如上圖(同樣是借用網上的圖),兩天前活動很低,這兩天是週末,所以從負載中大概可以猜出這是一個面向工作日使用的應用系統。同時觀察到但凡持續時間很久的時候,邏輯讀也很高,這個時候可以跳到【資源消耗量最大的幾個查詢】報表中查看具體的查詢。
我們也可以使用下面語句查看最近10天內持續時間最長的10個查詢:
SELECT TOP 10 qt.query_sql_text
,q.query_id
,so.name
,so.type
,SUM(rs.count_executions * rs.avg_duration) AS 'Total Duration'
FROM sys.query_store_query_text qt
INNER JOIN sys.query_store_query q ON qt.query_text_id = q.query_text_id
INNER JOIN sys.query_store_plan p ON q.query_id = p.query_id
INNER JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
INNER JOIN sys.query_store_runtime_stats_interval rsi ON rsi.runtime_stats_interval_id = rs.runtime_stats_interval_id
INNER JOIN sysobjects so ON so.id = q.object_id
WHERE rsi.start_time >= DATEADD(DAY, - 10, GETUTCDATE())
GROUP BY qt.query_sql_text
,q.query_id
,so.name
,so.type
ORDER BY SUM(rs.count_executions * rs.avg_duration_time) DESC
不過隨着全球化的發展,很多時間已經是以UTC來存儲,比如北京時間就是UTC+8。所以使用這個腳本時要注意轉換。
除此之外還可以分析出現了計劃迴歸的查詢、發現資源消耗情況、修改配置後的對比,其實在我看來,Query Store可以很好地用於出大部分的報告。
使用Query Store創建性能基線
首先我們得知道Query Store收集了什麼,哪些是我們需要的,哪些是要額外用其他工具收集的。
另外在收集和制定基線之前,記得先清空Query Store的數據以免數據混亂。可以通過GUI→數據庫→屬性→【查詢存儲】界面中的【清空Query Store】按鈕來清空。也可以執行:
USE [<Database>]
GO
EXEC sys.sp_query_store_flush_db;
清空之後,可以開始運行負載,最好是現實環境。
在收集了一定時間之後,通常根據你的收集間隔而定,比如15分鐘,那麼15分鐘後就可以停止或者開始新一輪的收集以便了解變化。
常見的使用是瞭解過去一小段時間特別是晚上發生了什麼事,因爲往往已經發生的事情要回查起來是更爲困難的。
但是有了Query Store,回看過去某個時間段的情況就很簡單了。
比如查看【資源消耗量最大的幾個查詢】中的配置,選擇特定的時間:
查找回歸的查詢
前面文章提到過,迴歸查詢使得同樣的查詢性能出現不一樣(通常是更差),產生迴歸的原因可能是統計信息的更新,數據分佈的變化,索引的變化等等。由於優化器會根據實際情況選擇更優的執行計劃,這就往往會導致查詢迴歸。這個時候就可以使用【迴歸的查詢】報表。
在這個報表中,你可以在右上角的窗口中選擇兩個計劃後,使用具有兩個計劃的查詢,並使用右上角的比較計劃按鈕。那麼執行計劃的差異部分就會高亮:
同時屬性也可以看出不一致:
T-SQL 查詢信息
Query Store本質上就是以存儲爲主,並顯示7中預設報表,但是還有很多數據沒有被利用出來,這個時候可以用一些T-SQL查詢來發現它的價值,下面列出幾個網上搜集的查詢,供讀者參考:
--查詢文本的總數
SELECT COUNT(*) AS CountQueryTextRows
FROM sys.query_store_query_text;
--查詢的總數
SELECT COUNT(*) AS CountQueryRows
FROM sys.query_store_query;
--唯一查詢(也就是不同的查詢)的數量
SELECT COUNT(DISTINCT query_hash) AS CountDifferentQueryRows
FROM sys.query_store_query;
--總執行計劃數
SELECT COUNT(*) AS CountPlanRows
FROM sys.query_store_plan;
--不同執行計劃的數量
SELECT COUNT(DISTINCT query_plan_hash) AS CountDifferentPlanRows
FROM sys.query_store_plan;
如果你發現CountQueryRows 和CountDifferentQueryRows 或者
CountPlanRows 和CountDifferentPlansRows之間存在差異,意味着你有一些類似的語句在運行,可能適合進行參數化。
--數據庫中10個最近被執行的查詢
SELECT
TOP 10 qt.query_sql_text, q.query_id, qt.query_text_id,
p.plan_id, rs.last_execution_time
FROM sys.query_store_query_text qt
JOIN sys.query_store_query q ON qt.query_text_id = q.query_text_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
ORDER BY rs.last_execution_time DESC
--獲取每個查詢的執行次數
SELECT
q.query_id, qt.query_text_id,
qt.query_sql_text,
SUM(rs.count_executions) AS total_execution_count
FROM sys.query_store_query_text qt
JOIN sys.query_store_query q ON qt.query_text_id = q.query_text_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
GROUP BY q.query_id, qt.query_text_id, qt.query_sql_text
ORDER BY total_execution_count DESC
--在最近一個小時內,平均執行時間最長的10個查詢
SELECT
TOP 10 qt.query_sql_text,
q.query_id, qt.query_text_id, p.plan_id,
GETUTCDATE() AS CurrentUTCTime,
rs.last_execution_time, rs.avg_duration
FROM sys.query_store_query_text qt
JOIN sys.query_store_query q ON qt.query_text_id = q.query_text_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
WHERE rs.last_execution_time > DATEADD(HOUR, -1, GETUTCDATE())
ORDER BY rs.avg_duration desc
--最近24小時內,10個平均物理I/O 讀最高的查詢
SELECT
TOP 10 qt.query_sql_text,
q.query_id, qt.query_text_id,
p.plan_id, rs.runtime_stats_id,
rsi.start_time, rsi.end_time,
rs.avg_physical_io_reads,
rs.avg_rowcount, rs.count_executions
FROM sys.query_store_query_text qt
JOIN sys.query_store_query q ON qt.query_text_id = q.query_text_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_runtime_stats rs ON p.plan_id = rs.plan_id
JOIN sys.query_store_runtime_stats_interval rsi
ON rsi.runtime_stats_interval_id = rs.runtime_stats_interval_id
WHERE rsi.start_time >= DATEADD(HOUR, -24, GETUTCDATE())
ORDER BY rs.avg_physical_io_reads desc
--最近性能倒退(迴歸)的查詢,條件是過去48小時內執行時間增長了一倍以上)
SELECT
qt.query_sql_text,
q.query_id,
p1.plan_id AS plan1,
rs2.avg_duration AS plan2
FROM sys.query_store_query_text qt
JOIN sys.query_store_query q ON qt.query_text_id = q.query_text_id
JOIN sys.query_store_plan p1 ON q.query_id = p1.query_id
JOIN sys.query_store_runtime_stats rs1 ON p1.plan_id = rs1.plan_id
JOIN sys.query_store_runtime_stats_interval rsi1 ON rsi1.runtime_stats_interval_id = rs1.runtime_stats_interval_id
JOIN sys.query_store_plan p2 ON q.query_id = p2.query_id
JOIN sys.query_store_runtime_stats rs2 ON p2.plan_id = rs2.plan_id
JOIN sys.query_store_runtime_stats_interval rsi2 ON rsi2.runtime_stats_interval_id = rs2.runtime_stats_interval_id
WHERE rsi1.start_time > DATEADD(HOUR, -48, GETUTCDATE()) AND
rsi2.start_time > rsi1.start_time AND
rs2.avg_duration > 2*rs1.avg_duration
--具有多個執行計劃的查詢
WITH QueryWithMultiplePlans
AS
(
SELECT COUNT(*) AS cnt, q.query_id
FROM sys.query_store_query_text qt
JOIN sys.query_store_query q ON qt.query_text_id = q.query_text_id
JOIN sys.query_store_plan p ON p.query_id = q.query_id
GROUP BY q.query_id HAVING COUNT(DISTINCT plan_id) > 1
)
SELECT q.query_id, OBJECT_NAME(object_id) AS ContainingObject,
query_sql_text, plan_id, p.query_plan AS plan_xml, p.last_compile_start_time,
p.last_execution_time
FROM QueryWithMultiplePlans qm
JOIN sys.query_store_query q ON qm.query_id = q.query_id
JOIN sys.query_store_plan p ON q.query_id = p.query_id
JOIN sys.query_store_query_text qt ON qt.query_text_id = q.query_text_id
ORDER BY query_id, plan_id
如果後續有好的腳本也會繼續補錄。
總結
本文介紹了Query Store的常用場景,就我個人而言,首先它收集的數據很全面,其次預設報表已經非常完善,可以應對大部分問題。然後在深入瞭解之後,還可以自己定義一些查詢來滿足需求。本人在工作中使用到的情景並不是非常多,所以暫時不擴展開來。一旦有一定數量和質量的積累,會及時更新。