Sql-Server

謂詞的直接值產生不太好的計劃

  • April 9, 2019

我正在使用 StackOverflow 轉儲來執行一些測試。

特別是,我正在查詢這張表:

文章表

我創建了這個索引:

創建索引

我正在執行以下查詢(只是強制索引來測試替代方案)

直接值查詢

我得到以下執行計劃的成本很高(66.63)。

不太好的計劃

這些是執行此查詢後的 IO 統計資訊:

在此處輸入圖像描述

然後,我通過提供變數而不是直接值來執行相同的查詢

使用變數查詢

我得到了一個更好的計劃(成本是 0.4385)。

更好的計劃

統計數據也更好:

更好的統計數據

起初…我認為 SQL Server 沒有將直接值辨識為 INT,但既沒有類型不匹配也沒有隱式轉換警告。

我還嘗試避免並行性,但在謂詞中傳遞直接值時,我仍然會使用 MAXDOP 1 獲得高成本計劃(和更高的 IO 統計資訊)。

在比較兩個計劃時,有不同的估計:

在此處輸入圖像描述

作為謂詞的一部分傳遞直接值有什麼問題?

作為謂詞的一部分傳遞直接值有什麼問題?

變數和參數的區別

優化器在計算變數的估計值時使用統計密度向量。

當“直接”或“靜態”值直接嵌入到查詢中時,將使用統計直方圖。這就是為什麼你會得到不同的估計,從而得到不同的計劃。

這是我的估計計劃:https ://www.brentozar.com/pastetheplan/?id=SJCduTuKN

在我 2010 年的 SO 數據庫副本中,OwnerUserId 列的密度為 0.000003807058。乘以 3,744,192 行 = 14.2544 行。這正是 IX_Posts_OwnerUserId 估計出來的行數。

索引搜尋估計的螢幕截圖

您可以通過執行以下 DBCC 命令獲取有關該索引的統計資訊的資訊:

DBCC SHOW_STATISTICS('dbo.Posts', 'IX_Posts_OwnerUserId');

這是(縮寫)輸出:

Name                    Updated             Rows
IX_Posts_OwnerUserId    Apr  8 2019  8:33AM 3744192

All density     Average Length  Columns
3.807058E-06    4               OwnerUserId

由於 PostTypeId 也是 WHERE 子句的一部分,因此也會自動為該列生成統計資訊。該密度向量為 0.25 x 3,744,192 行 = 936,048 行。

DBCC SHOW_STATISTICS('dbo.Posts', '_WA_Sys_00000010_0519C6AF');

和輸出:

Name                        Updated             Rows
_WA_Sys_00000010_0519C6AF   Apr  8 2019  9:04AM 3744192

All density Average Length  Columns
0.25        4               PostTypeId

由於這是一個“AND”謂詞,因此估計使用兩者中的較低值。

當您使用靜態值而不是變數時,它使用統計直方圖。這是該 SHOW_STATISTICS 命令的第三個結​​果集中。對於您正在使用的鍵,這是直方圖條目:

RANGE_HI_KEY    RANGE_ROWS  EQ_ROWS DISTINCT_RANGE_ROWS AVG_RANGE_ROWS
22656           13040       11371   305                 42.7541

這就是“靜態值”計劃中 11,371 的估計值的來源。

直方圖在很多時候可以是一個更好的估計,因為它可以更好地處理邊緣情況——因為像這樣的大表中經常會有一些異常值。

成本核算差異

在這種特定情況下,直方圖會產生一個完全正確的估計。生成的計劃的成本(正確地)高於使用密度向量的成本,因為它必須處理更多的行。

“低成本”計劃認為該搜尋將產生 14 行,而實際上產生了 11,371 行。

邏輯讀取

由於嵌套循環預取,並行計劃中的邏輯讀取略高。在我的機器上似乎並沒有太大的區別——查詢的經過時間在 10 毫秒之內。

並行性實際上沒有任何幫助,因為所有行最終都在一個執行緒上(無論如何在我的機器上)。添加OPTION (MAXDOP 1)有助於縮短執行時間,但不會刪除額外的邏輯讀取。

此查詢的“額外讀取”問題的一個潛在解決方案是通過將 PostTypeId 添加為包含列來完全避免鍵查找:

CREATE INDEX IX_Posts_OwnerUserId ON dbo.Posts (OwnerUserId) 
INCLUDE (PostTypeId) 
WITH (DROP_EXISTING = ON);

引用自:https://dba.stackexchange.com/questions/234096