效能調優11:查詢統計
資料庫引擎的工作流程可以歸納為接收請求、執行請求和返回結果。資料庫引擎每接收到一個新的查詢請求(Query Request),查詢優化器就會執行以下工作流程:編譯請求,生成執行計劃,並把執行計劃快取到記憶體中,執行計劃,最後向客戶端返回查詢的結果。把執行計劃儲存到記憶體的目的是為了複用執行計劃,減少編譯查詢請求的時間消耗和CPU消耗。當資料庫引擎再次接收到相同的查詢請求,資料庫引擎不需直接跳過編譯請求的過程,直接複用已經快取的執行計劃。
資料庫引擎並不是把查詢計劃永久儲存在記憶體中,而是會根據記憶體的壓力,智慧剔除一些建立時間早,並且複用頻次少的執行計劃。
一,語句控制代碼和計劃控制代碼
資料庫引擎要實現查詢計劃的複用,必須能夠識別查詢已經執行過,這就需要對查詢語句進行標記;查詢的執行計劃也會被標記,這就需要用到兩個唯一值:
- sql_handle:用以唯一標識一段TSQL文字(Batch或SP),TSQL文字儲存在SQL Manager Cache(SQLMGR)中。
- plan_handle:用於唯一標識一個已編輯的查詢計劃,查詢計劃儲存在計劃快取(Plan Cache)中。
sql_handle和plan_handle是如何生成的?
- 對於ad hoc查詢,sql_handle是基於整體的SQL Text生成的雜湊值;如果一個batch包含多個TSQL語句,那麼多個TSQL語句作為一個整體,batch中的查詢字句擁有相同的sql_handle值,但是有不同的偏移量。
- 對於執行的SP、觸發器或函式等資料庫物件,sql_handle是由database ID 和 object ID 派生的雜湊值。
- plan_handle是由整體(批處理或SP)生成的已編譯計劃派生的雜湊值。
sql_handle和plan_handle 之間具有1對多的關係。一個sql_handle 能夠生成多個查詢計劃,對應多個plan_handle,但是每個plan_handle只能對應一個sql_handle 。sql_handle對於每一個batch都是唯一的,但是,如果執行batch的條件發生改變,比如set 選項發生變化,那麼資料庫引擎在執行同樣的batch時,會生成新的執行計劃,產生新的plan_handle,但是sql_handle不變。想要了解更詳細的資訊,請閱讀《2.0 Sql_Handle and Plan_Handle Explained》。
1,SQL控制代碼
sql_handle是一個token,用於唯一標記查詢文字所屬的batch或sp,把sql_handle傳遞給 sys.dm_exec_sql_text()動態管理函式,並結合偏移 statement_start_offset和statement_end_offset,可以抽取出單個查詢的SQL文字。
函式 sys.dm_exec_sql_text(sql_handle | plan_handle)用於獲得整個Batch的TSQL文字,由於TSQL文字都是以nvarchar(max)型別儲存的,一個nvarchar是2個位元組,因此,一般情況下,位元組偏移量都是2的倍數。
2,計劃控制代碼
plan_handle是一個token,是整個Execution Plan的雜湊值,用於唯一標識一個batch或sp的執行計劃,把plan_handle傳遞給sys.dm_exec_query_plan(plan_handle)動態管理函式,可以獲取整體(batch或sp)的showplan。
3,查詢計劃(query plan)
查詢計劃是指查詢語句的顯示計劃(showplan),動態管理檢視 sys.dm_exec_query_plan 返回以XML格式表示的showplan,動態管理檢視 sys.dm_exec_text_query_plan 返回以文字格式表示的showplan:
sys.dm_exec_query_plan(plan_handle) sys.dm_exec_text_query_plan ( plan_handle , { statement_start_offset | 0 | DEFAULT } , { statement_end_offset | -1 | DEFAULT } )
文字方式可以指定batch中的單個TSQL語句,這需要指定該語句的偏移statement_start_offset 和 statement_end_offset。
二,抽取查詢語句
動態管理檢視 sys.dm_exec_query_stats 快取的是單個查詢語句的執行計劃,而sql_handle指向的是整個Batch或SP的控制代碼值,因此,在該檢視中,可能存在多個相同的sql_handle。
為了獲得單個查詢語句的文字,必須通過偏移量從整體(Batch語句)中抽取,偏移量的單位是位元組,位元組數量從0開始:
- statement_start_offset:語句開始偏移的位元組序號
- statement_end_offset:語句結束偏移的位元組序號,-1 表示TSQL文字的末尾
把sql_handle傳遞給 sys.dm_exec_sql_text()動態管理函式,並結合偏移 statement_start_offset和statement_end_offset,可以抽取出單個查詢的SQL文字,抽取查詢語句的指令碼是:
select substring(st.text ,qs.statement_start_offset/2+1, ( case when qs.statement_end_offset = -1 then len(convert(nvarchar(max), st.text)) else (qs.statement_end_offset - qs.statement_start_offset)/2 end ) ) as individual_query ,st.text as entire_query from sys.dm_exec_query_stats qs outer apply sys.dm_exec_sql_text(qs.sql_handle) as st
三,查詢的統計資料
資料庫引擎會把每一個查詢請求的執行資訊儲存起來,例如,查詢的文字,查詢等待的時長,執行的時間,消耗的資源等,並對這些資訊進行彙總和統計,這些彙總之後的資料就是查詢統計,儲存到記憶體結構 DMV:sys.dm_exec_query_stats中。在該檢視中,每一行資料都表示一個查詢語句的統計資料。
請求的執行資訊都經過彙總之後,儲存到DMV:sys.dm_exec_query_stats中,從該統計資料中,可以找出對效能影響最大的查詢請求,由於該DMV儲存的是累加值,在使用資料之前,一定要關注記錄的開始時間:
- creation_time:計劃編譯的時間
- last_execution_time:最近一次計劃開始執行的時間
這兩個時間表示查詢計劃的第一次執行和最後一次執行的時間戳。
1,檢視語句級別的統計資料
執行計劃的重編譯次數,執行查詢的總時間,邏輯讀和物理讀的次數等計數器,是觀察查詢執行情況的重要指標:
- plan_generation_num:表示執行計劃產生的數量,表示同一個TSQL文字重新編譯的次數;
- execution_count:計劃執行的次數
- total_elapsed_time:單詞elapsed是指單個語句執行的總時間,包括 waiting的時間或 CPU工作(worker)的時間,單位是微秒(us),一微秒是千分之一毫秒(ms)
- total_worker_time:CPU工作的總時間,單位是微秒(us)
- total_logical_reads:查詢計劃執行的邏輯讀的總次數;
- total_logical_writes:查詢計劃執行的邏輯寫的總次數;
- total_physical_reads:查詢計劃執行的物理讀的總次數;
- total_rows:查詢返回的資料行的總數量
- total_dop:併發執行的併發度的累加和
- total_grant_kb:該查詢計劃收到的預留授予記憶體(reserved memory grant)的總量,單位是KB
- total_used_grant_kb:該查詢計劃使用的預留授予記憶體(reserved memory grant)的總量,單位是KB
- total_ideal_grant_kb:該查詢計劃預估的理想授予記憶體(ideal memory grant)的總量,單位是KB
- total_splils:查詢計劃執行時,出現頁溢位的總頁數;
以下指令碼用於檢視執行計劃在單個語句級別上的平均資料,並按照平均執行時間排序,獲取 top 111 的資料:
select top 111 qs.execution_count, qs.total_rows/qs.execution_count as avg_rows, qs.total_worker_time/qs.execution_count/1000 as avg_worker_ms, qs.total_elapsed_time/qs.execution_count/1000 as avg_elapsed_ms, qs.total_physical_reads/qs.execution_count as avg_physical_reads, qs.total_logical_reads/qs.execution_count as avg_logical_reads, qs.total_logical_writes/qs.execution_count as avg_logical_writes, qs.creation_time, qs.plan_generation_num, --st.text as entire_query, substring(st.text, qs.statement_start_offset/2 + 1, ( case when qs.statement_end_offset = -1 then len(convert(nvarchar(max), st.text)) else (qs.statement_end_offset -qs.statement_start_offset)/2 end) ) as individual_query from sys.dm_exec_query_stats qs cross apply sys.dm_exec_sql_text(qs.sql_handle) as st order by avg_elapsed_ms desc
2,檢視儲存過程級別的查詢統計
對於快取的儲存過程,資料庫引擎把SP相關的統計資料快取在檢視:sys.dm_exec_procedure_stats 中,每一行資料都表示一個SP的統計資料:
select top 111 db_name(ps.database_id) as db_name ,ps.database_id ,object_schema_name(ps.object_id,ps.database_id)+'.'+object_name(ps.object_id,ps.database_id) as proc_name ,ps.type_desc as proc_type ,ps.cached_time ,ps.execution_count ,ps.total_worker_time/ps.execution_count/1000 as avg_worker_ms ,ps.total_elapsed_time/ps.execution_count/1000 as avg_elapsed_ms ,ps.total_physical_reads/ps.execution_count as avg_physical_reads ,ps.total_logical_reads/ps.execution_count as avg_logical_reads ,ps.total_logical_writes/ps.execution_count as avg_logical_writes from sys.dm_exec_procedure_stats ps where ps.database_id<32767 order by avg_elapsed_ms desc
對於database_id 為 32767,這個id是資源資料庫(Resource Database)預留的ID,一般情況下,使用者建立的資料庫ID都會小於該數值。
四,顯示被快取的計劃
函式 sys.dm_exec_query_plan 以XML格式返回指定batch或SP的查詢計劃,引數是plan_handle,這意味著,函式返回的是整個語句(Batch或SP)的showplan,XML格式是視覺化的,也可以返回文字格式的showplan。
select top 111 qs.execution_count, qs.total_rows/qs.execution_count as avg_rows, qs.total_worker_time/qs.execution_count/1000 as avg_worker_ms, qs.total_elapsed_time/qs.execution_count/1000 as avg_elapsed_ms, qs.total_physical_reads/qs.execution_count as avg_physical_reads, qs.total_logical_reads/qs.execution_count as avg_logical_reads, qs.total_logical_writes/qs.execution_count as avg_logical_writes, qs.creation_time, qs.plan_generation_num, st.text as entire_query, substring(st.text, qs.statement_start_offset/2 + 1, ( case when qs.statement_end_offset = -1 then len(convert(nvarchar(max), st.text)) else (qs.statement_end_offset -qs.statement_start_offset)/2 end) ) as individual_query, qp.query_plan from sys.dm_exec_query_stats qs cross apply sys.dm_exec_sql_text(qs.sql_handle) as st outer apply sys.dm_exec_query_plan(qs.plan_handle) as qp order by avg_elapsed_ms desc
五,計劃的統計資訊
動態管理檢視:sys.dm_exec_cached_plans 中,每一個行儲存一個查詢計劃,通過該檢視,可以檢視已快取的查詢計劃、查詢文字、快取計劃佔用的記憶體、快取計劃複用的次數等資訊。
select cp.refcounts ,cp.usecounts ,cp.size_in_bytes ,cp.cacheobjtype ,cp.objtype ,st.text as batch_sql --,cp.plan_handle from sys.dm_exec_cached_plans cp outer apply sys.dm_exec_sql_text(cp.plan_handle) st
參考文件:
Execution Related Dynamic Management Views and Functions (Transact-