直方圖外的基數估計
設置
我在理解基數估計時遇到了一些麻煩。這是我的測試設置:
- Stack Overflow 數據庫的 2010 版
- SQL Server 2017 CU15+GDR (KB4505225) - 14.0.3192.2
- 新的 CE(兼容級別 140)
我有這個過程:
USE StackOverflow2010; GO CREATE OR ALTER PROCEDURE #sp_PostsByCommentCount @CommentCount int AS BEGIN SELECT * FROM dbo.Posts p WHERE p.CommentCount = @CommentCount OPTION (RECOMPILE); END; GO
表上沒有非聚集索引或統計資訊
dbo.Posts
(有一個聚集索引Id
)。當詢問對此的估計計劃時,得出的“估計行數”
dbo.Posts
為 1,934.99:EXEC #sp_PostsByCommentCount @CommentCount = 51;
當我詢問估計計劃時,自動創建了以下統計對象:
DBCC SHOW_STATISTICS('dbo.Posts', [_WA_Sys_00000006_0519C6AF]);
其中的亮點是:
- 統計數據的採樣率相當低,為 1.81% (67,796 / 3,744,192)
- 僅使用了 31 個直方圖步驟
- “所有密度”值為
0.03030303
(採樣了 33 個不同的值)RANGE_HI_KEY
直方圖中的最後一個是 50,其中EQ_ROWS
1問題
傳遞任何高於 50 的值(最多並包括 2,147,483,647)會導致 1,934.99 行估計。 使用什麼計算或值來產生這個估計? 順便說一下,遺留基數估計器產生 1 行的估計值。
我試過的
以下是我的一些理論,我嘗試過的事情,或者我在研究這個問題時能夠探勘到的其他資訊。
密度向量
我最初認為這將是密度向量,就像我使用
OPTION (OPTIMIZE FOR UNKNOWN)
. 但是這個 stats 對象的密度向量是 3,744,192 * 0.03030303 = 113,460,所以不是這樣。擴展活動
我嘗試執行一個收集
query_optimizer_estimate_cardinality
事件的擴展事件會話(我從 Paul White 的部落格文章Cardinality Estimation: Combining Density Statistics中了解到),並得到了這些有趣的花絮:<CalculatorList> <FilterCalculator CalculatorName="CSelCalcColumnInInterval" Selectivity="-1.000" CalculatorFailed="true" TableName="[p]" ColumnName="CommentCount" /> <FilterCalculator CalculatorName="CSelCalcAscendingKeyFilter" Selectivity="0.001" TableName="[p]" ColumnName="CommentCount" UseAverageFrequency="true" StatId="4" /> </CalculatorList>
所以看起來
CSelCalcAscendingKeyFilter
計算器被使用了(另一個說它失敗了,不管那是什麼意思)。此列不是鍵,也不是唯一的,也不是必須升序的,但無論如何。對該術語進行一些Google搜尋使我找到了一些部落格文章:
這些文章表明新的 CE 將這些直方圖之外的估計值基於密度向量和統計數據的修改計數器的組合。不幸的是,我已經排除了密度向量(我認為?!),並且修改計數器為零(
sys.dm_db_stats_properties
無論如何)。跟踪標誌
Forrest建議我打開 TF 2363 以獲取有關估計過程的更多資訊。我認為該輸出中最相關的是:
Plan for computation: CSelCalcAscendingKeyFilter(avg. freq., QCOL: [p].CommentCount) Selectivity: 0.000516798
這是一個突破(謝謝,Forrest!):這個
0.000516798
數字(似乎在Selectivity="0.001"
上面的 XE 屬性中被舍入了)乘以表中的行數是我一直在尋找的估計值(1,934.99)。我可能遺漏了一些明顯的東西,但我無法逆向工程如何在
CSelCalcAscendingKeyFilter
計算器內部產生該選擇性值。
根據我的測試,越界基數估計只是行數的平方根,以自上次統計更新以來添加的行數為界,以每個值的平均行數為界。
在您的情況下,1,934.99 = SQRT(3744192)
測試設置如下:
--setup USE TestDB ALTER DATABASE [TestDB] SET AUTO_UPDATE_STATISTICS OFF GO DROP TABLE IF EXISTS dbo.Hist CREATE TABLE dbo.Hist ( ID int identity primary key, Num int ) INSERT dbo.Hist SELECT TOP 300 (ROW_NUMBER() OVER(ORDER BY(SELECT 1/0)))%3 FROM master..spt_values a CROSS JOIN master..spt_values b
--Get estimated plan --don't forget to run right after setup to auto-create stats SELECT * FROM dbo.Hist WHERE Num = 1000
--gradually add rows, then rerun estimate above INSERT dbo.Hist SELECT TOP 100 -1 FROM master..spt_values a
--I sure hope you weren't testing this in prod (cleanup) ALTER DATABASE [TestDB] SET AUTO_UPDATE_STATISTICS ON GO
令人驚訝的是,這種方法生成的行估計數:400 行時 20 行,900 行時 30 行,1600 行時 40 行,等等。
儘管超過 10000,行估計最大為 100,這是現有統計資訊中每個值的行數。僅添加 10 行會將估計值設置為 10,因為 sqrt(300) > 10。
因此,估計值可以用這個公式表示:
Estimate = MIN(SQRT(AC), MIN(AR, MC))
請注意,如果對統計數據進行抽樣,則不考慮 MC。於是公式就變成了:
Estimate = MIN(SQRT(AC), AR))
在哪裡
- MC 是“修改計數”(自創建統計數據以來的修改數)
- AC 是“調整後的基數”(統計數據中的行數加上 MC),
- AR 是每個值的平均行數(統計中的行數除以列中的不同值)
這些估計的公式,以及有關計算器的其他詳細資訊,可以在這篇部落格文章中找到:分析來自 CSelCalcAscendingKeyFilter 計算器的估計