RDSSQLServer-專題分享-巧用執行計劃快取之索引缺失
title: RDS SQL Server – 專題分享 – 巧用執行計劃快取之索引缺失
author: 風移
摘要
執行計劃快取是MSSQL Server記憶體管理十分重要的部分,同樣如何巧用執行計劃快取來解決我們平時遇到的一系列問題也是一個值得深入研究的專題。這篇文章是如何巧用執行計劃快取的開篇,分享如何使用執行計劃快取來分析索引缺失(Missing Indexes)。
問題引入
缺失索引是SQL Server CPU使用率居高不下的第一大殺手,也是SQL Server資料庫非常大的潛在風險點。在之前的高CPU使用率系列文章中,我們分享了使用系統動態檢視的方法來獲取索引缺失的方法,詳情請戳:RDS SQL Server – 最佳實踐 – 高CPU使用率系列之索引缺失。那麼有沒有其他的方法既可以獲取到缺失索引,還能夠展示相應查詢語句執行計劃中有價值的詳細資訊呢?這篇文章從執行計劃快取的角度和視野來獲取缺失索引,並且對相應執行計劃有價值的資訊進行了詳細展示,包括單不僅限於:
建立缺失索引對查詢效能的提升預估百分比
執行計劃針對的查詢語句、資料庫物件
執行計劃建立時間和最後使用時間
執行計劃快取大小、編譯時間、CPU和記憶體消耗
最小、最大、最後一次和總共消耗CPU的時間
最小、最大、最後一次和總共IO物理、邏輯讀寫
最小、最大、最後一次和總共影響的行數
……
場景分析
MSSQL Server引擎,在執行特定語句時,需要對語句進行語法檢查、語義分析、編譯、最佳執行路徑選擇、生成執行計劃和快取執行計劃,以便下次執行相同語句時,可以直接從執行計劃快取中獲取執行計劃,以節約效能開銷和提升查詢語句執行效能。執行計劃快取中有非常多有價值的資訊,那麼我們如何有效利用執行計劃快取來幫助我們分析系統存在的潛在風險和效能問題呢?本篇文章分享巧用執行計劃快取來獲取缺失索引。
測試環境
測試環境搭建和相應查詢語句參見之前的文章RDS SQL Server – 最佳實踐 – 高CPU使用率系列之索引缺失中的測試環境和執行查詢部分,在此不再累述。
解決方法
前面做了很多鋪墊關於背景的介紹,執行計劃快取基礎理論,終於到了激動人心的解決方法部分了。一言不合,直接上程式碼:
USE master
GO
DECLARE
@EngineEdition INT = CAST(SERVERPROPERTY(N`EngineEdition`) AS INT)
;
;WITH XMLNAMESPACES (DEFAULT `http://schemas.microsoft.com/sqlserver/2004/07/showplan`)
,planCache
AS(
SELECT
*
FROM sys.dm_exec_query_stats as qs WITH(NOLOCK)
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) as qp
WHERE qp.query_plan.exist(`/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple/QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex`)=1
), analyedPlanCache
AS(
SELECT
sql_text = T.C.value(`(@StatementText)[1]`, `nvarchar(max)`)
,[impact%] = T.C.value(`(./QueryPlan/MissingIndexes/MissingIndexGroup/@Impact)[1]`, `float`)
,cachedPlanSize = T.C.value(`(./QueryPlan/@CachedPlanSize)[1]`, `int`)
,compileTime = T.C.value(`(./QueryPlan/@CompileTime)[1]`, `int`)
,compileCPU = T.C.value(`(./QueryPlan/@CompileCPU)[1]`, `int`)
,compileMemory = T.C.value(`(./QueryPlan/@CompileMemory)[1]`, `int`)
,database_name = T.C.value(`(./QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex/@Database)[1]`,`sysname`)
,schema_name = T.C.value(`(./QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex/@Schema)[1]`,`sysname`)
,object_name = T.C.value(`(./QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex/@Table)[1]`,`sysname`)
,equality_columns = (
SELECT
DISTINCT REPLACE(REPLACE(tb.col.value(`(@Name)[1]`, `sysname`), N`]`, N``), N`[`, N``) + `,`
FROM T.c.nodes(`./QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex/ColumnGroup`) AS T(cg)
CROSS APPLY T.cg.nodes(`./Column`) AS tb(col)
WHERE T.cg.value(`(@Usage)[1]`, `sysname`) = `EQUALITY`
FOR XML PATH(``)
)
,inequality_columns = (
SELECT
DISTINCT REPLACE(REPLACE(tb.col.value(`(@Name)[1]`, `sysname`), N`]`, N``), N`[`, N``) + `,`
FROM T.c.nodes(`./QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex/ColumnGroup`) AS T(cg)
CROSS APPLY T.cg.nodes(`./Column`) AS tb(col)
WHERE T.cg.value(`(@Usage)[1]`, `sysname`) = `INEQUALITY`
FOR XML PATH(``)
)
,include_columns = (
SELECT
DISTINCT REPLACE(REPLACE(tb.col.value(`(@Name)[1]`, `sysname`), N`]`, N``), N`[`, N`@`) + `,`
FROM T.c.nodes(`./QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex/ColumnGroup`) AS T(cg)
CROSS APPLY T.cg.nodes(`./Column`) AS tb(col)
WHERE T.cg.value(`(@Usage)[1]`, `sysname`) = `INCLUDE`
FOR XML PATH(``)
)
,pc.*
FROM planCache AS pc
CROSS APPLY query_plan.nodes(`/ShowPlanXML/BatchSequence/Batch/Statements/StmtSimple`) AS T(C)
WHERE C.exist(`./QueryPlan/MissingIndexes/MissingIndexGroup/MissingIndex`) = 1
)
SELECT
plan_handle
,query_plan
,query_hash
,query_plan_hash
,sql_text
,[impact%]
,cachedplansize
,compileTime
,compileCPU
,compileMemory
,object = database_name + `.` + schema_name + `.` + object_name
,miss_index_creation =
N`CREATE NONCLUSTERED INDEX ` + QUOTENAME(N`IX_` + REPLACE(LEFT(equality_columns, len(equality_columns) - 1), N`,`, N`_`) + `_`
+ REPLACE(LEFT(inequality_columns, len(inequality_columns) - 1), N`,`, N`_`) + `_`
+ REPLACE(LEFT(include_columns, len(include_columns) - 1), N`,`, N`_`), `[]`)
+ N` ON ` + database_name + `.` + schema_name + `.` + object_name
+ QUOTENAME(
CASE
WHEN equality_columns is not null and inequality_columns is not null
THEN equality_columns + LEFT(inequality_columns, len(inequality_columns) - 1)
WHEN equality_columns is not null and inequality_columns is null
THEN LEFT(equality_columns, len(equality_columns) - 1)
WHEN inequality_columns is not null
THEN LEFT(inequality_columns, len(inequality_columns) - 1)
END
, `()`)
+ CASE
WHEN include_columns is not null
THEN ` INCLUDE ` + QUOTENAME(REPLACE(LEFT(include_columns, len(include_columns) - 1), N`@`, N``), N`()`)
ELSE ``
END
+ N` WITH (FILLFACTOR = 90`
+ CASE @EngineEdition
WHEN 3 THEN N`,ONLINE = ON`
ELSE ``
END + `);`
,creation_time
,last_execution_time
,execution_count
,total_worker_time
,last_worker_time
,min_worker_time
,max_worker_time
,total_physical_reads
,last_physical_reads
,min_physical_reads
,max_physical_reads
,total_logical_writes
,last_logical_writes
,min_logical_writes
,max_logical_writes
,total_logical_reads
,last_logical_reads
,min_logical_reads
,max_logical_reads
,total_clr_time
,last_clr_time
,min_clr_time
,max_clr_time
,total_elapsed_time
,last_elapsed_time
,min_elapsed_time
,max_elapsed_time
,total_rows
,last_rows
,min_rows
,max_rows
FROM analyedPlanCache
執行完畢以後的結果展示如下,由於結果集太長,人為分為四段結果集:
第一段結果集截圖
第二段結果集截圖
第三段結果集截圖
第四段結果集截圖
點開第一個張截圖中的其中一行query_plan xml,我們檢視到的Missing Indexes資訊節點:
從截圖中,我們同樣可以找到非常有用的資訊,包括:
建立索引後的效能提升為99.8369%(第11行)
缺失索引的資料庫物件,包括資料庫名,架構名和表名稱(第12行)
相等謂詞使用的缺失索引列(第13行)
不相等謂詞使用的缺失索引列(第16行)
覆蓋欄位的缺失索引列(第19行)
注意事項
由於執行計劃緩是儲存在SQL OS的記憶體中,所以會隨著以下動作被自動或被動清空:
SQL Server Service重啟
作業系統重啟
人為清空快取
系統感覺到記憶體壓力自動回收等
當這些動作發生以後,再通過執行計劃快取來獲取有效資訊,可能會導致資訊獲取不完整。所以,使用本篇文章方法獲取缺失索引資訊之前,請確保你的SQL Server系統已經充分Warm Up。
最後總結
這篇文章是巧用執行計劃快取系列文章的開篇,詳細講解了如何使用執行計劃快取來獲取缺失索引資訊以及執行計劃的一些有價值的詳細資訊,以此來破解RDS SQL Server高CPU使用率的問題。
本文的視訊演示,我已經上傳到Youku,詳情請戳 MSSQL Server巧用執行計劃快取獲取缺失索引資訊
相關文章
- RDSSQLServer-專題分享-巧用執行計劃快取之Single-usedplansSQLServer快取
- 【Oracle】-【索引-HINT,執行計劃】-帶HINT的索引執行計劃Oracle索引
- mysql的執行計劃快取問題MySql快取
- Oracle 索引和執行計劃Oracle索引
- mysql索引和執行計劃MySql索引
- mysql 執行計劃索引分析筆記MySql索引筆記
- Oracle訪問索引的執行計劃(一)Oracle索引
- Oracle訪問索引的執行計劃(二)Oracle索引
- Oracle訪問索引的執行計劃(三)Oracle索引
- Oracle訪問索引的執行計劃(四)Oracle索引
- Oracle訪問索引的執行計劃(五)Oracle索引
- 建立索引調整sql的執行計劃索引SQL
- 關於索引的執行計劃記載索引
- sql執行計劃變更和刪除快取中執行計劃的方法SQL快取
- 【MySQL】MySQL的執行計劃及索引優化MySql索引優化
- 建了索引執行計劃會有區別了索引
- 理解索引:MySQL執行計劃詳細介紹索引MySql
- 索引及排序對執行計劃的影響索引排序
- 執行計劃-1:獲取執行計劃
- 【專案管理經驗分享】為什麼專案計劃難以完美執行?專案管理
- 分享一下使用專案管理軟體管理專案計劃及執行專案管理
- 執行計劃問題導致處理速度時快時慢的問題
- clustering factor索引聚簇因子和執行計劃索引
- 分割槽索引(Partition Index)與SQL執行計劃(中)索引IndexSQL
- 分割槽索引(Partition Index)與SQL執行計劃(下)索引IndexSQL
- 分割槽索引(Partition Index)與SQL執行計劃(上)索引IndexSQL
- 執行計劃沒變,執行時快時慢是怎麼回事?
- 執行計劃
- 為什麼該SQL的執行計劃不走索引???SQL索引
- 索引失效系列——統計量過期引起執行計劃錯誤索引
- Flutter快取之mmkvFlutter快取
- 分析執行計劃優化SQLORACLE的執行計劃(轉)優化SQLOracle
- oracle sqlprofile 固定執行計劃,並遷移執行計劃OracleSQL
- 【sql調優之執行計劃】獲取執行計劃SQL
- 交流(1)-- 執行計劃錯誤問題
- oracle執行計劃------未走索引,隱式轉換的坑Oracle索引
- SYBASE執行計劃
- MySQL 執行計劃MySql