如何在 SQL Server 中提示多對多連接?
我有 3 個“大”表,它們連接在一對列(兩者
int
)上。
- 表 1 有約 2 億行
- 表 2 有約 150 萬行
- Table3 有大約 600 萬行
每個表在 , 上都有一個聚集索引
Key1
,Key2
然後是一列。Key1
具有低基數並且非常偏斜。它總是在WHERE
子句中引用。Key2
條款中從未提及WHERE
。每個連接都是多對多的。問題在於基數估計。每個連接的輸出估計變得更小而不是更大。當實際結果達到數百萬時,這導致最終估計為低數百。
我有什麼辦法可以讓行政長官做出更好的估計嗎?
SELECT 1 FROM Table1 t1 JOIN Table2 t2 ON t1.Key1 = t2.Key1 AND t1.Key2 = t2.Key2 JOIN Table3 t3 ON t1.Key1 = t3.Key1 AND t1.Key2 = t3.Key2 WHERE t1.Key1 = 1;
我嘗試過的解決方案:
- 在 上創建多列統計資訊
Key1
,Key2
- 創建大量過濾後的統計資訊
Key1
(這很有幫助,但我最終會在數據庫中獲得數千個使用者創建的統計資訊。)屏蔽的執行計劃(抱歉屏蔽不好)
在我正在查看的情況下,結果有 900 萬行。新的 CE 估計有 180 行;舊版 CE 估計有 6100 行。
這是一個可重現的範例:
DROP TABLE IF EXISTS #Table1, #Table2, #Table3; CREATE TABLE #Table1 (Key1 INT NOT NULL, Key2 INT NOT NULL, T1Key3 INT NOT NULL, CONSTRAINT pk_t1 PRIMARY KEY CLUSTERED (Key1, Key2, T1Key3)); CREATE TABLE #Table2 (Key1 INT NOT NULL, Key2 INT NOT NULL, T2Key3 INT NOT NULL, CONSTRAINT pk_t2 PRIMARY KEY CLUSTERED (Key1, Key2, T2Key3)); CREATE TABLE #Table3 (Key1 INT NOT NULL, Key2 INT NOT NULL, T3Key3 INT NOT NULL, CONSTRAINT pk_t3 PRIMARY KEY CLUSTERED (Key1, Key2, T3Key3)); -- Table1 WITH Numbers AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number) FROM master..spt_values t1 CROSS JOIN master..spt_values t2), DataSize (Key1, NumberOfRows) AS (SELECT 1, 2000 UNION SELECT 2, 10000 UNION SELECT 3, 25000 UNION SELECT 4, 50000 UNION SELECT 5, 200000) INSERT INTO #Table1 SELECT Key1 , Key2 = ROW_NUMBER() OVER (PARTITION BY Key1, T1Key3 ORDER BY Number) , T1Key3 FROM DataSize CROSS APPLY (SELECT TOP(NumberOfRows) Number , T1Key3 = Number%(Key1*Key1) + 1 FROM Numbers ORDER BY Number) size; -- Table2 (same Key1, Key2 values; smaller number of distinct third Key) WITH Numbers AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number) FROM master..spt_values t1 CROSS JOIN master..spt_values t2) INSERT INTO #Table2 SELECT DISTINCT Key1 , Key2 , T2Key3 FROM #Table1 CROSS APPLY (SELECT TOP (Key1*10) T2Key3 = Number FROM Numbers ORDER BY Number) size; -- Table2 (same Key1, Key2 values; smallest number of distinct third Key) WITH Numbers AS (SELECT TOP (1000000) Number = ROW_NUMBER() OVER(ORDER BY t1.number) FROM master..spt_values t1 CROSS JOIN master..spt_values t2) INSERT INTO #Table3 SELECT DISTINCT Key1 , Key2 , T3Key3 FROM #Table1 CROSS APPLY (SELECT TOP (Key1) T3Key3 = Number FROM Numbers ORDER BY Number) size; DROP TABLE IF EXISTS #a; SELECT col = 1 INTO #a FROM #Table1 t1 JOIN #Table2 t2 ON t1.Key1 = t2.Key1 AND t1.Key2 = t2.Key2 WHERE t1.Key1 = 1; DROP TABLE IF EXISTS #b; SELECT col = 1 INTO #b FROM #Table1 t1 JOIN #Table2 t2 ON t1.Key1 = t2.Key1 AND t1.Key2 = t2.Key2 JOIN #Table3 t3 ON t1.Key1 = t3.Key1 AND t1.Key2 = t3.Key2 WHERE t1.Key1 = 1;
需要明確的是,優化器已經知道這是一個多對多連接。如果您強制合併連接並查看估計的計劃,您可以看到連接運算符的屬性,它告訴您連接是否可以是多對多的。您需要在這裡解決的問題是提高基數估計值,大概是這樣您就可以為您遺漏的查詢部分獲得更有效的查詢計劃。
我要嘗試的第一件事是將連接的結果
Object3
放入Object5
臨時表中。對於您發布的計劃,它只是 51393 行上的一列,因此它幾乎不應該佔用 tempdb 中的任何空間。您可以在臨時表上收集完整的統計數據,僅此一項就足以獲得足夠準確的最終基數估計。收集完整的統計數據Object1
也可能有所幫助。當您從右到左遍歷計劃時,基數估計通常會變得更糟。如果這不起作用,
ENABLE_QUERY_OPTIMIZER_HOTFIXES
如果您尚未在數據庫或伺服器級別啟用查詢提示,則可以嘗試查詢提示。Microsoft 將影響計劃的 SQL Server 2016 性能修復鎖定在該設置後面。其中一些與基數估計有關,因此也許您會很幸運,其中一個修復程序將對您的查詢有所幫助。您還可以嘗試使用帶有FORCE_LEGACY_CARDINALITY_ESTIMATION
查詢提示的舊基數估計器。某些數據集可能會使用舊版 CE 獲得更好的估計。作為最後的手段,您可以使用 Adam Machanic 的
MANY()
函式通過您喜歡的任何因素手動增加基數估計。我在另一個答案中談論它,但看起來連結已經死了。如果您有興趣,我可以嘗試探勘一些東西。