1. 程式人生 > >效能調優11:查詢統計

效能調優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-