首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> SQL Server 資料庫 >> 關於SqlServer

SQL Server 執行計劃快取深度解析(上):機制、物件與效能診斷

     編輯:關於SqlServer

SQL 敘述句本質是邏輯宣告,而非物理存取路徑。當 T-SQL 送達 SQL Server,查詢處理器依序經歷:解析 → 綁定 → 優化 → 執行。前三階段產出「執行計劃」,最後階段依計劃存取資料。本文聚焦執行計劃生成後的快取機制、儲存物件、效能診斷,以及快取與最佳化器的核心衝突。

為何需要執行計劃快取?

編譯執行計劃耗費大量 CPU 與記憶體,主要工作三階段:

  1. 解析與綁定:識別物件名稱、建立邏輯運算樹。
  2. 優化簡化:子查詢轉聯結、謂詞下推、移除多餘聯結。
  3. 成本評估:依統計資訊進行 Cost-based 評估,從候選計劃中挑選「足夠好」而非「最佳」計劃。

同一邏輯查詢可能對應多種物理路徑(如聯結順序),評估所有路徑成本本身即昂貴。因此快取編譯成果、重用計劃成為提升整體吞吐的關鍵。

快取儲存的四大物件類型

物件類型說明共用特性
Compiled Plan (編譯計劃)經優化的執行計劃本體,類似 .NET 的 MSIL多使用者共用
Execution Context (執行內容)執行時的環境狀態:SET 選項、區域變數值等每次執行獨立,關聯同一 Compiled Plan
Cursor (游標)游標狀態資訊游標專屬連線,但底層計劃可共用
Algebra Tree (代數樹/解析樹)查詢文本的樹狀表示,供視圖、預設值、約束重用跨查詢共用,避免重複解析

透過 sys.dm_exec_cached_plans 可檢視快取計劃清單:

記憶體佔用細節可查 sys.dm_os_memory_cache_counters:

快取淘汰機制:內部與外部記憶體壓力

  • 外部壓力: Buffer Pool 可用空間低於臨界值(視實體記憶體或 max server memory 而定)。
  • 內部壓力: 快取物件數量超過門檻(32 位元 40,000;64 位元 160,000)。

淘汰演算法以記憶體壓力為主導,非單純 LRU。

關鍵最佳實踐:物件名稱必須加上結構描述

快取鍵值為查詢文本本身。SELECT * FROM dbo.Table 與 SELECT * FROM Table 會產生兩份計劃。永遠在物件名稱前冠上結構描述名稱,避免不必要的計劃重複。

善用快取進行效能診斷:找出最耗時查詢

快取保留豐富統計資訊(CPU、等待時間、執行次數),且不含編譯時間。以下查詢可快速定位實例啟動以來總耗時最長的 20 條查詢:

SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT TOP 20
    CAST(qs.total_elapsed_time / 1000000.0 AS DECIMAL(28,2)) AS [Total Duration (s)],
    CAST(qs.total_worker_time * 100.0 / qs.total_elapsed_time AS DECIMAL(28,2)) AS [% CPU],
    CAST((qs.total_elapsed_time - qs.total_worker_time) * 100.0 / qs.total_elapsed_time AS DECIMAL(28,2)) AS [% Waiting],
    qs.execution_count,
    CAST(qs.total_elapsed_time / 1000000.0 / qs.execution_count AS DECIMAL(28,2)) AS [Avg Duration (s)],
    SUBSTRING(qt.text, (qs.statement_start_offset/2)+1,
        ((CASE WHEN qs.statement_end_offset = -1
            THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2
            ELSE qs.statement_end_offset END - qs.statement_start_offset)/2) + 1) AS [Individual Query],
    qt.text AS [Parent Query],
    DB_NAME(qt.dbid) AS DatabaseName,
    qp.query_plan
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE qs.total_elapsed_time > 0
ORDER BY qs.total_elapsed_time DESC;

優於 Trace/Profiler:零額外負擔、即時可查。但有盲點:索引重建、統計更新不快取;快取隨時被淘汰;無法累計未參數化同邏輯查詢的統計。

核心衝突:參數嗅探與計劃重用的博弈

最佳化器依統計資訊為特定參數值生成最優計劃(如小範圍用 Index Seek,大範圍用 Index Scan)。但快取機制將首次編譯計劃重用於後續所有參數值。

經典災難案例:

  • 預存程序首次以 @TerritoryID = 4(少量資料)執行 → 快存 Index Seek 計劃。
  • 後續以 @TerritoryID = 5(大量資料)執行 → 強制重用 Index Seek → 執行 5000+ 次 Key Lookup,效能崩潰。
  • 若執行順序顛倒,則首次快存 Index Scan,小範圍查詢改為全表掃描,同樣低效。

這就是「最佳化器想要精準」與「快取想要重用」的根本衝突。下篇將詳細探討解決方案。

小結

  • 編譯成本高 → 快取重用為核心效能手段。
  • 四類快取物件,Compiled Plan 為主體。
  • 快取鍵值 = 查詢文本 → 必須加結構描述名稱。
  • DMV 診斷查詢可替代 Trace,但有盲點。
  • 參數嗅探導致計劃不穩定,是快取與最佳化器衝突的核心表現。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved