iT邦幫忙

2026 iThome 鐵人賽

DAY 27
0
自我挑戰組

SQL Server 基礎&調教系列 第 27

【效能調教】 27.查詢重新編譯

  • 分享至 

  • xImage
  •  

為了盡量降低編譯執行計畫所產生的額外負擔,執行計畫會儲存在一個稱為「計畫快取(Plan Cache)」的記憶體空間中。

當使用預備陳述式(Prepared Statements)、預存程序(Stored Procedures),或其他建立參數化查詢的機制時,系統便能重複利用快取中的執行計畫。

然而,有許多情況可能導致執行計畫從快取中被移除。有時候這是好事,例如資料已經變更、統計資料已經更新,或系統發生某些變化,使得採用不同的執行計畫可能改善效能。

但有時候這也可能是壞事。系統可能發生大量重新編譯,對處理器造成過高負載,並干擾系統中查詢原本正常且良好的執行行為。

上一篇說明如何盡可能地降低編譯的情況發生,這裡會講重新編譯的好壞處、如何識別正在重新編譯的語法、分析重新編譯的原因、避免重新編譯的方法

重新編譯的優缺點

編譯是一個成本很高的操作,但是隨資料時間的改變,統計資料也會改變、資料分布改變、新增索引或條件約束等等因素,最佳化器為了找到更好的執行計畫,重新編譯是必然發生的事情。

還有,因為重新編譯都是在查詢層級,而不是整個預存程序,因此會產生兩個結果

  1. 跟重新編譯預存程序相比,你可能會看到比編譯預存程序更高的重新編譯次數
  2. 但是因為編譯的範圍僅限於查詢,所以跟編譯預存程序相比,消耗的時間跟資源更少。

然後再 Quert Store 那邊提到過的強制執行計畫,如果有套用,標準的重新編譯其實還是會進行只是編譯後不套用,她會套用強制執行的那一個計畫。

這是因為如果被強制的那個計畫因為結構性變更或其他原因被標記成無效,系統就會改用重新編譯的那個執行計畫。

所以再重新編譯的角度來看,強制執行計畫在這裡沒有幫助。

--為了說明重新編譯可能帶來的好處,先建立一個示範 sp 
CREATE OR ALTER PROCEDURE dbo.WorkOrder
AS
SELECT
    wo.WorkOrderID AS N'工單編號',
    wo.ProductID   AS N'產品編號',
    wo.StockedQty  AS N'入庫數量'
FROM Production.WorkOrder AS wo
WHERE wo.StockedQty BETWEEN 500 AND 700;

https://ithelp.ithome.com.tw/upload/images/20260827/20118581myBxHXuOsV.png
如果去執行她就會看到這個執行計畫。

首先這個計畫非常爛,我會在下一篇討論索引效能,這裡只要先知道這個計畫超爛。

最佳化在執行計畫上面給了一個遺漏索引提示,她建議建立一個索引,不過她建議的不是我接下來要做的這個。

--建立索引之後再跑一次 sp
CREATE INDEX IX_Test
ON Production.WorkOrder
(
    StockedQty,
    ProductID
);

https://ithelp.ithome.com.tw/upload/images/20260827/20118581HYm1MbQam2.png
當索引建立完成後,SQL Server 會自動將所有參考 Production.WorkOrder 資料表的執行計畫標記為需要重新編譯。

這表示最佳化工具現在可以在重新產生執行計畫時,評估是否使用剛建立的新索引。

直到真的執行的時候,才會真的重新編譯。

在這裡她會去重新編譯的主要原因之一,是統計資料更新。

--示範玩了 把她刪掉
DROP INDEX Production.WorkOrder.IX_Test;
--下一個示範
CREATE OR ALTER PROCEDURE dbo.WorkOrderAll
AS

-- 此處刻意使用 SELECT * 作為範例
SELECT *
FROM Production.WorkOrder AS wo;

這邊我故意用 SELECT * ,這樣為了滿足這個查詢,最佳化只有一條路就是 INDEX SCAN。

在執行這個 SP 之前,先建立一個擴充事件,來擷取重新編譯

CREATE EVENT SESSION [QueryAndRecompile]
ON SERVER
    ADD EVENT sqlserver.rpc_completed
    (
        WHERE ([sqlserver].[database_name] = N'AdventureWorks2022')
    ),
    ADD EVENT sqlserver.rpc_starting
    (
        WHERE ([sqlserver].[database_name] = N'AdventureWorks2022')
    ),
    ADD EVENT sqlserver.sp_statement_completed
    (
        WHERE ([sqlserver].[database_name] = N'AdventureWorks2022')
    ),
    ADD EVENT sqlserver.sp_statement_starting
    (
        WHERE ([sqlserver].[database_name] = N'AdventureWorks2022')
    ),
    ADD EVENT sqlserver.sql_batch_completed
    (
        WHERE ([sqlserver].[database_name] = N'AdventureWorks2022')
    ),
    ADD EVENT sqlserver.sql_batch_starting
    (
        WHERE ([sqlserver].[database_name] = N'AdventureWorks2022')
    ),
    ADD EVENT sqlserver.sql_statement_completed
    (
        WHERE ([sqlserver].[database_name] = N'AdventureWorks2022')
    ),
    ADD EVENT sqlserver.sql_statement_recompile
    (
        WHERE ([sqlserver].[database_name] = N'AdventureWorks2022')
    ),
    ADD EVENT sqlserver.sql_statement_starting
    (
        WHERE ([sqlserver].[database_name] = N'AdventureWorks2022')
    )
    ADD TARGET package0.event_file
    (
        SET filename = N'QueryAndRecompile'
    )
WITH
(
    TRACK_CAUSALITY = ON
);
GO

ALTER EVENT SESSION QueryAndRecompile
ON SERVER
STATE = START;

當這個 Extended Events 工作階段啟動並開始運作後

--先執行一次
EXEC dbo.WorkOrderAll;
GO

CREATE INDEX IX_Test
ON Production.WorkOrder
(
    StockedQty,
    ProductID
);
GO

EXEC dbo.WorkOrderAll;
-- 建立 IX_Test 索引之後再次執行

https://ithelp.ithome.com.tw/upload/images/20260827/201185813gKPuPXPql.png
可以看這個索引對整個查詢並沒有影響,但是因為統計資訊變了,所以最佳化還是重新去編譯這個 sp 查詢,浪費時間,結果都一樣。

識別正在被重新編譯的陳述式

在正常的 sp 中,通常會包含很多 select 之類的東西,要所以要精準判斷到底是哪一條在發生重新編譯,並不容易。

這也是為什麼我建立了那一個擴充事件。

那個擴充事件會結取 :

  • Batch 的開始與完成事件
  • 預存程序內陳述式的開始與完成事件
  • 一般 SQL 陳述式的開始與完成事件
  • 重新編譯事件
  • Causality Tracking 因果關聯追蹤資訊

透過這些事件,就可以把整個執行流程串在一起,判斷是哪一條陳述式觸發重新編譯。

接下來就要詳細的說明這個擴充事件做了什麼
https://ithelp.ithome.com.tw/upload/images/20260827/201185819wBKZAh96G.png
第一個事件是 sql_batch_starting ( 上面那個 set statistics xml on 那是額外開統計的 跟這裡要說得無關 )
整個流程如下

  1. sql_batch_starting
    batch 開始執行,也就是 SSMS 送出這個 Batch

EXEC dbo.WorkOrderAll;

2. sql_statement_starting 
    batch 中的陳述式開始執行,因為只有一條陳述式
```sql
EXEC dbo.WorkOrderAll;
  1. sp_statement_starting
    開始執行 sp 內部的陳述式
SELECT * 
FROM pRODUCTION.WorkOrder;
但是,因為先前有做過建 index 這個動作,所以 schema 變了,就被標註成需要重新編譯。
  1. sql_statement_recompile
    發生陳述式層級重新編譯
    這邊重新編譯的原因她會寫 : schema changed

  2. 第二次 sp_statement_starting
    sp 陳述式重新執行
    因為第一次開始執行時,被重新編譯事件中斷,所以重新編譯完成後,SQL Server 再次啟動這條陳述式。

  3. sp_statement_completed
    第一個完成 這是內部 select * 這個完成

  4. sql_statement_completed
    第二個完成,這是外部 BATCH SP 完成

  5. sql_batch_completed
    整個 batch 完

上面擷取這麼多只是為了演示一遍 sp 實際再運作的流程。
一般在找問題的時候,只會擷取 sql_statement_recompile 而已

這裡就有一個小東西要分享
一般來說 recompile 確實都包在查詢裡面發生,所以去擷取的時候可以看到 statement 裡面寫這次重編譯是從哪一個查詢來的。

但是也有例外就是我現在舉的例子

statement 是 null
所以這時候就要把完整的事件都抓進來看,你才有可能看出這個重編譯是從哪一個查詢來的。

還有,由於這是測試的環境,所以可以很簡單的用眼睛去看 timestamp 順序,就看出整個事件的流程。

但在正式環境中,需要利用 Causality Tracking 把事件分組排序,才能找到要看的地方。

分析重新編譯的原因

雖然重新編譯可以透過建立更合適的執行計劃來改善效能,但重新編義也完全可能變得過度頻繁,並嚴重影響效能。

每一次執行計畫編譯都會消耗寶貴的 cpu 時間。還有執行計畫也會在記憶體中移來移去,這也是需要代價。
基於以上原因,我們應該了解那些條件會導致重新編譯、何時發生。

造成陳述式層級重新編譯的部分原因如下 :

  • 結構描述變更(Schema Changes):如果陳述式所參考的資料表、暫存資料表或檢視表發生變更,包括結構、中繼資料與索引的變更,就必須重新編譯。
  • 繫結變更(Binding Changes):當資料表或暫存資料表中某個資料行的繫結發生變更,例如預設值變更。
  • 統計資料更新(Statistics Updates):當查詢所使用的統計資料被更新時,不論是自動更新或手動更新。
  • 延遲物件解析(Deferred Object Resolution):如果查詢所需的物件是在 Batch 執行過程中才建立,就必須重新編譯。查詢可以在物件尚不存在的情況下完成編譯,但當該物件建立後,參考它的陳述式就需要重新編譯。
  • SET 選項(SET Options):當指定查詢的 SET 選項發生變更時。
  • sp_recompile:明確呼叫系統預存程序 sp_recompile,會導致重新編譯。
  • RECOMPILE Hint:使用 RECOMPILE 查詢提示,其效果正如名稱所表示的那樣。
  • 參數敏感型計畫(Parameter-Sensitive Plans,PSP):當多計畫查詢中的其中一個計畫重新編譯,或 Dispatcher Plan 發生變更時。
  • 選擇性參數最佳化(Optional Parameter Optimization,OPO):如果所產生的其中一個計畫被重新編譯,或 Dispatcher Plan 發生變更,其情況與 PSP 相同。

PSP跟OPO之後會再說,現在只需要知道,這兩個東西會讓一個查詢對應多個執行計畫,但是這些執行製化的編譯規則,還是跟前面一樣,沒有別的特殊狀況。

--這可以看一下所有重新編譯的原因
SELECT dxmv.map_value
FROM sys.dm_xe_map_values AS dxmv
WHERE dxmv.name = 'statement_recompile_cause';

這個原因會因為 SQL Server 不斷加入新功能,這份清單也會持續增加。
要討論每一個細項會佔用太多篇幅,有興趣就 AI 問一問就好

絕大多數都很容易理解 例如

延遲物件解析 ( Deferred Object Resolution )

在一個 batch 中,動態建立資料庫物件,然後在後續陳述式中使用這些物件,是非常常見的情況。

當這類 batch 第一次執行時,初始執行計畫不會包含正在建立之物件的相關資訊。其他處理策略會延後到查詢實際執行時才決定。

當執行一條參考這些新建立物件的 DML 時,查詢就會重新編譯,然後產生新的執行計畫。

一般資料表與區域暫存資料表都可以在 Batch 中建立,用來保存中間結果集。

不過,因延遲物件解析而造成的陳述式重新編譯,在一般資料表與區域暫存資料表上的行為並不相同。

資料表上的重新編譯

CREATE OR ALTER PROC dbo.RecompileTable
AS
CREATE TABLE dbo.ProcTest1
(
    C1 INT
);

SELECT *
FROM dbo.ProcTest1;

DROP TABLE dbo.ProcTest1;

像這種 sp,因為在第一次執行這個 sp 的時候,SQL Server 會在查詢真正執行之前先產生執行計畫。
但是此時,table proctest1 並不存在,因此所建立的執行計畫不會包含 select 的處理策略。
所以建立 table 之後,必須再進行重新編譯
他流程可以用剛剛建立的擴充事件去看
https://ithelp.ithome.com.tw/upload/images/20260827/20118581AKUaYSKOlL.png

暫存資料表上的重新編譯
這個在現實運營上是一個更常見的情況

CREATE OR ALTER PROC dbo.RecompileProc
AS
CREATE TABLE #TempTable (C1 INT);

INSERT INTO #TempTable (C1)
VALUES (42);

https://ithelp.ithome.com.tw/upload/images/20260827/20118581zTPWhjD4YA.png
如果建立這個預存程序,然後執行兩次,擴充事件會長這樣
可以看到中間有 Deferred compile

預存程序中的第一條陳述式會建立暫存資料表,第二條陳述式則會將資料插入其中。第二條陳述式必須延後到物件實際建立之後才能進行編譯。

然而,與上一節範例中的一般資料表不同,建立暫存資料表不會被視為結構描述變更。因此,不需要再次重新編譯,並且可以重複使用原本的執行計畫。

避免重新編譯

反覆強調,重新編譯對特定查詢可能帶來極大好處。如果資料已經發生足夠程度的變化,最佳化重新編譯就可以做出更好的執行計畫。

所以不是要無腦的避免重新編譯,只是有些程式的撰寫方式會造成不必要的重新編譯。

因此,遵循一些錯法,可以降低重新編譯發生的頻率 :

  • 避免交錯使用 DDL 與 DML 陳述式。
  • 減少因統計資料變更而造成的重新編譯。
  • 使用 KEEPFIXED PLAN 提示。
  • 停用資料表的自動統計資料維護。
  • 使用資料表變數。
  • 跨多個作用域使用暫存資料表。
  • 避免在 Batch 內變更 SET 選項。

避免交錯使用 DDL 和 DML

在 batch 或 sp 中使用 temp table,是很常見的作法。

實際上,甚至會看到有人在處理過程中還去修改結構描述或新增索引。

但,這些做法會影響執行計畫的有效性,會讓參考這些 temp table 的陳述式發生重新編譯,而其中大多數是由延遲編譯所造成的。

類似下面這種狀況

CREATE OR ALTER PROC dbo.TempTable
AS
-- 所有陳述式一開始都會先完成編譯
CREATE TABLE #MyTempTable
(
    ID  INT,
    Dsc NVARCHAR(50)
);

-- 這條陳述式必須重新編譯
INSERT INTO #MyTempTable
(
    ID,
    Dsc
)
SELECT
    pm.ProductModelID,
    pm.Name
FROM Production.ProductModel AS pm;

-- 這條陳述式必須重新編譯
SELECT
    mtt.ID,
    mtt.Dsc
FROM #MyTempTable AS mtt;

CREATE CLUSTERED INDEX iTest
ON #MyTempTable (ID);

-- 建立索引會造成重新編譯
SELECT
    mtt.ID,
    mtt.Dsc
FROM #MyTempTable AS mtt;

CREATE TABLE #t2
(
    c1 INT
);

-- 因建立新資料表而重新編譯
SELECT c1
FROM #t2;

記住,預存程序中的每一條陳述式,一開始都會取得一個執行計畫。
儘管物件尚不存在,他也會先編譯,等到物件真的建立之後,他就又要編譯一次。

減少因統計資料變更造成的重新編譯

在大多數情況下,資料隨時間發生變化並導致統計資料更新時,查詢需要產生新的執行計畫。這表示重新編譯所帶來的成本,整體而言通常對系統是有益的。

所以再次強調,統計資料變更造成的重新編譯,是正確的。

然而,在某些情況下,即使統計資料已經更新,因為資料分布仍然相同,重新產生的執行計畫可能與原本完全一致。如果這種狀況頻繁發生,重新編譯所造成的負擔可能會非常明顯。

必須先說這種狀況非常少見,但是如果真的發生,可以用下列兩種方式處理由統計資料更新所引起的重新編譯 :

  1. 停用 KEEPFIXED PLAN 查詢提示
  2. 停用 TABLE 的自動統計資料維護

使用 KEEPFIXED PLAN 提示

如果希望可以盡可能避免重新編譯,可以套用這個 KEEPFIXED PLAN 查詢提示。
這個可以讓查詢需要

IF
(
    SELECT OBJECT_ID('dbo.Test1')
) IS NOT NULL
    DROP TABLE dbo.Test1;
GO

CREATE TABLE dbo.Test1
(
    C1 INT,
    C2 CHAR(50)
);

INSERT INTO dbo.Test1
VALUES
(1, '2');

CREATE NONCLUSTERED INDEX IndexOne
ON dbo.Test1 (C1);
GO

-- 建立參考前述資料表的預存程序
CREATE OR ALTER PROC dbo.TestProc
AS
SELECT
    t.C1,
    t.C2
FROM dbo.Test1 AS t
WHERE t.C1 = 1
OPTION (KEEPFIXED PLAN);
GO

-- 在資料表只有 1 筆資料時,第一次執行預存程序
EXEC dbo.TestProc; -- 第一次執行

-- 新增大量資料列,以造成統計資料變更
WITH Nums
AS
(
    SELECT 1 AS n

    UNION ALL

    SELECT Nums.n + 1
    FROM Nums
    WHERE Nums.n < 1000
)
INSERT INTO dbo.Test1
(
    C1,
    C2
)
SELECT
    1,
    Nums.n
FROM Nums
OPTION (MAXRECURSION 1000);
GO

-- 在統計資料發生變更後,再次執行預存程序
EXEC dbo.TestProc;

完全沒有重新編譯發生

任何查詢提示都應該在經過充分測試,並證明它確實是最佳解決方案之後才使用。

KEEPFIXED PLAN 可能會讓系統持續保留一個較差的執行計畫,而重新編譯後原本可能產生更好的計畫與更佳效能。

另一個在這裡可能有用的查詢提示是 KEEP PLAN

這個提示是專門針對暫存資料表使用的。它會保留目前的執行計畫,直到統計資料更新所需的 500 筆資料列門檻被達到為止。

使用暫存資料表時,它可以幫助減少重新編譯的次數。

不過,它仍然具有與前面相同的注意事項。

停用 table 的自動統計資料維護

可以選擇停用統計資料更新,範圍可以是整個資料庫,也可以只針對個別資料表

EXEC sys.sp_autostats 'dbo.Test1' ,'OFF';

現在,不論資料如何變更,這個資料表上的統計資料都不會更新。

這表示,不會有任何查詢因為資料變更而被標記為需要重新編譯。

再一次強調,這種做法可能會造成非常嚴重的問題。在實作之前,應進行充分測試,以確認它不會損害其他查詢的效能。

此外,如果你確實選擇停用自動統計資料更新,就應該規劃一套手動更新統計資料的流程,並以更可控的方式處理由此產生的重新編譯。

還有一個辦法是,變數 TABLE

這跟 TEMP TABLE 很類似的東西,但差別在變數 TABLE 他不會有統計資料

所以不會遇到因為統計資料更新造成重新編譯的問題

DECLARE @count INT;

CREATE TABLE #TempTable
(
    C1 INT PRIMARY KEY
);

SET @count = 1;

WHILE @count < 8
BEGIN
    INSERT INTO #TempTable
    (
        C1
    )
    VALUES
    (
        @count
    );

    SELECT
        tt.C1
    FROM #TempTable AS tt
    JOIN Production.ProductModel AS pm
        ON pm.ProductModelID = tt.C1
    WHERE tt.C1 < @count;

    SET @count += 1;
END;

DROP TABLE #TempTable;

跨多個作用域使用暫存資料表

你可以在一個預存程序中宣告暫存資料表,然後在由第一個預存程序呼叫的第二個預存程序中,使用同一個暫存資料表。

在 SQL Server 2019 以前的版本中,以及 Azure SQL Database 以外的環境中,這種做法會導致查詢每次被呼叫時都發生重新編譯。

然而,由於資料庫引擎已經有所改變,2019以後不會再看到這些重新編譯。

CREATE OR ALTER PROC dbo.OuterProc
AS
CREATE TABLE #Scope
(
    ID        INT PRIMARY KEY,
    ScopeName VARCHAR(50)
);

EXEC dbo.InnerProc;
GO

CREATE OR ALTER PROC dbo.InnerProc
AS
INSERT INTO #Scope
(
    ID,
    ScopeName
)
VALUES
(
    1,            -- ID - int
    'InnerProc'   -- ScopeName - varchar(50)
);

SELECT
    s.ScopeName
FROM #Scope AS s;
GO

避免在批次內變更 SET 選項

在執行預存程序時變更環境設定,會直接導致重新編譯。

為了符合 ANSI 相容性,一般建議將下列 SET 選項保持為 ON

  • ARITHABORT
  • CONCAT_NULL_YIELDS_NULL
  • QUOTED_IDENTIFIER
  • ANSI_NULLS
  • ANSI_PADDING
  • ANSI_WARNINGS

NUMERIC_ROUNDABORT 則應設定為 OFF

第一次執行這些查詢時,位於 SET 選項變更之後的陳述式會發生重新編譯。

不過,注意到第二次執行時沒有出現任何重新編譯。這是因為這些 SET 選項現在已經成為執行計畫的一部分,因此不再需要進一步重新編譯。

然而,對於內容相同的查詢,現在計畫快取中已經存在三個執行計畫。

另外值得注意的是,變更 SET NOCOUNT 環境設定不會造成重新編譯。

控制重新編譯的結果

前面是幾種可以嘗試減少重新編譯次數的方法
但是有一些重新編譯是無法避免。在這種情況下,通常需要一些機制來控制重新編譯後產生的結果。

有四種選擇

  • 強制執行計畫
  • 計畫指南
  • 查詢提示
  • 強制提示

強制執行計畫

這個在前面有稍微提到過

在後面介紹如何處理參數敏感型執行計畫的時候,還會再進一步說明

強制執行計畫不會阻止重新編譯的發生,只要符合前面列出的任何條件,執行計劃仍然會重新編譯。

但是,強制執行計畫可以讓我們控制重新編譯的結果。系統不會使用一個全新的執行計畫,而是使用你所選擇並強制指定的計畫。

這裡的前提是,該執行計畫沒有因程式碼或結構變更而失效。否則,這就是控制重新編譯結果的一種方式。

查詢提示

再說一次這不是提示,他英文是 QUERY HINT,但這個東西更像是一個命令,叫SQL SERVER 必須得這樣做。

前面有介紹過 KEEPFIXED PLAN 消除重新編譯,和 KEEP PLAN 減少 TEMP TABLE 重新編譯發生次數,那個用的就是 QUERY HINT。

許多可用的查詢提示,都直接與強制最佳化工具採用特定選擇有關。後續還會介紹多種不同的提示。不過,這裡有一個我特別想提出來說明的提示:OPTIMIZE FOR

OPTIMIZE FOR 提示可以讓你控制編譯過程中所使用的參數值。你可以搭配特定參數值使用 OPTIMIZE FOR,以取得針對該值產生的精確執行計畫。你也可以使用 OPTIMIZE FOR UNKNOWN,以取得較為通用的執行計畫。

--示範參數敏感 sp
--什麼是參數敏感以後再說 
CREATE OR ALTER PROCEDURE dbo.CustomerList
    @CustomerID INT
AS
SELECT
    soh.SalesOrderNumber,
    soh.OrderDate,
    sod.OrderQty,
    sod.LineTotal
FROM Sales.SalesOrderHeader AS soh
JOIN Sales.SalesOrderDetail AS sod
    ON soh.SalesOrderID = sod.SalesOrderID
WHERE soh.CustomerID >= @CustomerID
OPTION (OPTIMIZE FOR (@CustomerID = 1));

這樣寫的話,這個預存程序中的查詢都會根據 OPTIMIZE FOR 提示中提供給 @CustomerID 的值,取得同一個執行計畫。

--然後再去跑這個看執行計畫
EXEC dbo.CustomerList
    @CustomerID = 7920
    WITH RECOMPILE;

EXEC dbo.CustomerList
    @CustomerID = 30118
    WITH RECOMPILE;

id = 7920 那個預估 121317 筆,實際回傳也是 121317 筆,簡單來說,這個計畫對這個參數而言是正確的。

但是 id = 30118 那個實際只回傳 289 筆資料,這強烈表示,第二個參數值原本可能可以用不同的執行計畫,只是因為 Query Hint 把他鎖在只能用這個執行計畫。

計畫指南

如果要用查詢提示,但又不想改程式碼的話,可以用這個計畫指南

CREATE OR ALTER PROCEDURE dbo.CustomerList
    @CustomerID INT
AS
SELECT
    soh.SalesOrderNumber,
    soh.OrderDate,
    sod.OrderQty,
    sod.LineTotal
FROM Sales.SalesOrderHeader AS soh
JOIN Sales.SalesOrderDetail AS sod
    ON soh.SalesOrderID = sod.SalesOrderID
WHERE soh.CustomerID >= @CustomerID;

然後今天判斷出要加查詢提示,但因為某些原因無法修改程式碼,此時就可以去建立一個計畫指南

sp_create_plan_guide
    @name = N'MyGuide',
    @stmt = N'SELECT soh.SalesOrderNumber,
                     soh.OrderDate,
                     sod.OrderQty,
                     sod.LineTotal
              FROM Sales.SalesOrderHeader AS soh
              JOIN Sales.SalesOrderDetail AS sod
                  ON soh.SalesOrderID = sod.SalesOrderID
              WHERE soh.CustomerID >= @CustomerID;',
    @type = N'OBJECT',
    @module_or_batch = N'dbo.CustomerList',
    @params = NULL,
    @hints = N'OPTION (OPTIMIZE FOR (@CustomerID = 1))';

但是要注意,查詢文字、格式都要一樣,換行、空白那些的也是,全部都要一樣。
上面這種方式是物件指南,只有再物件 CustomerList 裡面才會發生
也是有另外一種 SQL 指南的類型

SELECT
    soh.SalesOrderNumber,
    soh.OrderDate,
    sod.OrderQty,
    sod.LineTotal
FROM Sales.SalesOrderHeader AS soh
JOIN Sales.SalesOrderDetail AS sod
    ON soh.SalesOrderID = sod.SalesOrderID
WHERE soh.CustomerID >= 1;
EXECUTE sp_create_plan_guide
    @name = N'MyGoodSQLGuide',
    @stmt = N'SELECT
                  soh.SalesOrderNumber,
                  soh.OrderDate,
                  sod.OrderQty,
                  sod.LineTotal
              FROM Sales.SalesOrderHeader AS soh
              JOIN Sales.SalesOrderDetail AS sod
                  ON soh.SalesOrderID = sod.SalesOrderID
              WHERE soh.CustomerID >= 1;',
    @type = N'SQL',
    @module_or_batch = NULL,
    @params = NULL,
    @hints = N'OPTION
               (
                   TABLE HINT
                   (
                       soh,
                       FORCESEEK
                   )
               )';
--移除指南
EXECUTE sp_control_plan_guide
    @operation = 'Drop',
    @name = N'MyGoodSQLGuide';

EXECUTE sp_control_plan_guide
    @operation = 'Drop',
    @name = N'MyGuide';

最後一種就是強制提示,這個前面有寫過了。

Recompile 原因 說明 是否正常
Statistics changed 統計資料更新導致重新編譯 正常
Schema changed Table / Index / Column 結構變更 正常
Temp table changed 暫存表結構或資料量變化 常見
SET option changed SET 選項不同導致 plan 不能重用 要注意
OPTION (RECOMPILE) 明確要求每次重新編譯 人為控制
Plan removed from cache 記憶體壓力或清除 cache 視情況
Deferred compile Temp table / table variable 延遲編譯 視情況

上一篇
【效能調教】 26.執行計畫快取
下一篇
【效能調教】 28.索引結構
系列文
SQL Server 基礎&調教30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言