Azure SQL DB/DW 系列(14)——使用Query Store(3)——常用場景

本文屬於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的常用場景,就我個人而言,首先它收集的數據很全面,其次預設報表已經非常完善,可以應對大部分問題。然後在深入瞭解之後,還可以自己定義一些查詢來滿足需求。本人在工作中使用到的情景並不是非常多,所以暫時不擴展開來。一旦有一定數量和質量的積累,會及時更新。

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