यह निश्चित रूप से अनपेक्षित व्यवहार की तरह लगता है। यह सच है कि कार्डिनैलिटी के अनुमानों को किसी योजना के प्रत्येक चरण में संगत होने की आवश्यकता नहीं है, लेकिन यह अपेक्षाकृत सरल क्वेरी प्लान है और अंतिम कार्डिनैलिटी का अनुमान इस बात से असंगत है कि क्वेरी क्या कर रही है। इस तरह के कम कार्डिनैलिटी के अनुमान के कारण खराब विकल्प और अन्य जटिल योजनाओं में अन्य तालिकाओं के लिए पहुँच विधियों के लिए खराब विकल्प हो सकते हैं।
परीक्षण और त्रुटि के माध्यम से हम कुछ समान प्रश्नों के साथ आ सकते हैं, जिनके लिए समस्या दिखाई नहीं देती है:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT -1)
END AS ID2
FROM dbo.X_HEAP;
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END AS ID2
FROM dbo.X_HEAP;
हम अधिक प्रश्नों के साथ भी आ सकते हैं जिसके लिए यह समस्या दिखाई देती है:
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE)
END AS ID2
FROM dbo.X_HEAP;
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT -1)
END AS ID2
FROM dbo.X_HEAP;
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END AS ID2
FROM dbo.X_HEAP;
ऐसा प्रतीत होता है कि एक पैटर्न है: यदि उसके भीतर एक अभिव्यक्ति है, CASE
जिसे निष्पादित किए जाने की उम्मीद नहीं है और परिणाम अभिव्यक्ति एक तालिका के खिलाफ एक उपश्रेणी है तो पंक्ति का अनुमान उस अभिव्यक्ति के बाद 1 हो जाता है।
यदि मैं किसी तालिका के विरुद्ध क्वेरी को एक संकुल सूचकांक के साथ लिखता हूं तो नियम कुछ बदल जाते हैं। हम एक ही डेटा का उपयोग कर सकते हैं:
CREATE TABLE dbo.X_CI (ID INT NOT NULL, PRIMARY KEY (ID))
INSERT INTO dbo.X_CI WITH (TABLOCK)
SELECT * FROM dbo.X_HEAP;
UPDATE STATISTICS X_CI WITH FULLSCAN;
इस क्वेरी में 1000 पंक्ति अंतिम अनुमान है:
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
END
FROM dbo.X_CI;
लेकिन इस क्वेरी में 1 पंक्ति अंतिम अनुमान है:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END
FROM dbo.X_CI;
आगे की खुदाई करने के लिए, हम इस बारे में जानकारी प्राप्त करने के लिए अनिच्छुक ट्रेस ध्वज 2363 का उपयोग कर सकते हैं कि क्वेरी ऑप्टिमाइज़र ने चयनात्मकता की गणना कैसे की। मुझे यह पता लगाने में मदद मिली कि अविभाजित ट्रेस ध्वज 8606 के साथ उस ट्रेस ध्वज को जोड़ा जाना चाहिए । TF 2363 परियोजना के सामान्यीकरण के बाद सरलीकृत पेड़ और पेड़ दोनों के लिए चयन संगणना देता है। दोनों झंडे सक्षम होने से यह स्पष्ट हो जाता है कि कौन सी गणना किस पेड़ पर लागू होती है।
चलिए इसे प्रश्न में पोस्ट की गई मूल क्वेरी के लिए आज़माते हैं:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2)
END AS ID2
FROM X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
यहाँ आउटपुट का हिस्सा है जो मुझे लगता है कि कुछ टिप्पणियों के साथ प्रासंगिक है:
Plan for computation:
CSelCalcColumnInInterval -- this is the type of calculator used
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID -- this is the column used for the calculation
Pass-through selectivity: 0 -- all rows are expected to have a true value for the case expression
Stats collection generated:
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter) -- the row estimate after the join will still be 1000
CStCollBaseTable(ID=1, CARD=1000 TBL: X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: X_OTHER_TABLE)
...
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 1 -- no rows are expected to have a true value for the case expression
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1 x_jtLeftOuter) -- the row estimate after the join will still be 1
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter) -- here is the row estimate after the previous join
CStCollBaseTable(ID=1, CARD=1000 TBL: X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: X_OTHER_TABLE_2)
अब आइए इसे एक समान क्वेरी के लिए आज़माएं जिसमें समस्या नहीं है। मैं इस एक का उपयोग करने जा रहा हूँ:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT -1)
END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
बहुत अंत में डिबग आउटपुट:
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 1
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollConstTable(ID=4, CARD=1) -- this is different than before because we select a constant instead of from a table
आइए एक और क्वेरी की कोशिश करें जिसके लिए खराब पंक्ति अनुमान मौजूद है:
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE)
END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
बहुत अंत में कार्डिनैलिटी का अनुमान 1 पंक्ति पर चला जाता है, फिर पास-थ्रू सेलेक्टिविटी = 1. के बाद कार्डिनैलिटी का अनुमान 0.501 और 0.499 की चयनात्मकता के बाद संरक्षित होता है।
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 0.501
...
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 0.499
...
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 1
Stats collection generated:
CStCollOuterJoin(ID=12, CARD=1 x_jtLeftOuter) -- this is associated with the ELSE expression
CStCollOuterJoin(ID=11, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=10, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
CStCollBaseTable(ID=4, CARD=1 TBL: X_OTHER_TABLE)
चलिए फिर से एक और सिम्युलर क्वेरी पर स्विच करते हैं जिसमें समस्या नहीं है। मैं इस एक का उपयोग करने जा रहा हूँ:
SELECT
ID
, CASE
WHEN ID < 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
WHEN ID >= 500
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END AS ID2
FROM dbo.X_HEAP
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
डिबग आउटपुट में कभी भी एक चरण नहीं होता है जिसमें पास-थ्रू की चयनात्मकता होती है। कार्डिनैलिटी का अनुमान 1000 पंक्तियों पर रहता है।
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_HEAP].ID
Pass-through selectivity: 0.499
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_HEAP)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
End selectivity computation
क्वेरी के बारे में क्या है जब इसमें एक क्लस्टर इंडेक्स वाली तालिका शामिल होती है? पंक्ति अनुमान समस्या के साथ निम्नलिखित प्रश्न पर विचार करें:
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
END
FROM dbo.X_CI
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
डिबग आउटपुट का अंत हम पहले से ही देखे गए समान है:
Plan for computation:
CSelCalcColumnInInterval
Column: QCOL: [SE_DB].[dbo].[X_CI].ID
Pass-through selectivity: 1
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
हालांकि, इस मुद्दे के बिना CI के खिलाफ क्वेरी का अलग आउटपुट है। इस क्वेरी का उपयोग करना:
SELECT
ID
, CASE
WHEN ID = 0
THEN (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE_2)
ELSE (SELECT TOP 1 ID FROM dbo.X_OTHER_TABLE)
END
FROM dbo.X_CI
OPTION (QUERYTRACEON 3604, QUERYTRACEON 2363, QUERYTRACEON 8606);
विभिन्न कैलकुलेटर का उपयोग किया जा रहा है। CSelCalcColumnInInterval
अब प्रकट नहीं होता है:
Plan for computation:
CSelCalcFixedFilter (0.559)
Pass-through selectivity: 0.559
Stats collection generated:
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
...
Plan for computation:
CSelCalcUniqueKeyFilter
Pass-through selectivity: 0.001
Stats collection generated:
CStCollOuterJoin(ID=9, CARD=1000 x_jtLeftOuter)
CStCollOuterJoin(ID=8, CARD=1000 x_jtLeftOuter)
CStCollBaseTable(ID=1, CARD=1000 TBL: dbo.X_CI)
CStCollBaseTable(ID=2, CARD=1 TBL: dbo.X_OTHER_TABLE_2)
CStCollBaseTable(ID=3, CARD=1 TBL: dbo.X_OTHER_TABLE)
अंत में, हम निम्नलिखित शर्तों के तहत अधीनता के बाद एक खराब पंक्ति अनुमान प्राप्त करते हैं:
CSelCalcColumnInInterval
चयनात्मकता कैलकुलेटर प्रयोग किया जाता है। मुझे नहीं पता कि इसका उपयोग कब किया गया है, लेकिन यह अधिक बार दिखाई देता है जब आधार तालिका एक ढेर होती है।
पास-थ्रू चयनात्मकता = 1. दूसरे शब्दों में, CASE
सभी पंक्तियों के लिए गलत में से एक भाव का मूल्यांकन किया जाना अपेक्षित है। इससे कोई फर्क नहीं पड़ता कि पहली CASE
अभिव्यक्ति सभी पंक्तियों के लिए सही का मूल्यांकन करती है।
इसमें बाहरी जुड़ाव है CStCollBaseTable
। दूसरे शब्दों में, CASE
परिणाम अभिव्यक्ति एक तालिका के खिलाफ एक उपश्रेणी है। एक निरंतर मान काम नहीं करेगा।
शायद उन स्थितियों में क्वेरी ऑप्टिमाइज़र अनायास ही नेस्टेड लूप के आंतरिक भाग पर किए गए कार्य के बजाय बाहरी तालिका के पंक्ति अनुमान के लिए पास-थ्रू चयनात्मकता लागू कर रहा है। यह पंक्ति अनुमान को घटाकर 1 कर देगा।
मैं दो वर्कअराउंड खोजने में सक्षम था। मैं APPLY
एक सबक्वेरी के बजाय उपयोग करते समय इस मुद्दे को पुन: पेश करने में सक्षम नहीं था । ट्रेस फ्लैग 2363 का आउटपुट बहुत अलग था APPLY
। प्रश्न में मूल प्रश्न को फिर से लिखने का एक तरीका इस प्रकार है:
SELECT
h.ID
, a.ID2
FROM X_HEAP h
OUTER APPLY
(
SELECT CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2)
END
) a(ID2);
विरासत CE के रूप में अच्छी तरह से इस मुद्दे से बचने के लिए प्रकट होता है।
SELECT
ID
, CASE
WHEN ID <> 0
THEN (SELECT TOP 1 ID FROM X_OTHER_TABLE)
ELSE (SELECT TOP 1 ID FROM X_OTHER_TABLE_2)
END AS ID2
FROM X_HEAP
OPTION (USE HINT('FORCE_LEGACY_CARDINALITY_ESTIMATION'));
इस मुद्दे के लिए एक कनेक्ट आइटम प्रस्तुत किया गया था (कुछ विवरण जो पॉल व्हाइट ने अपने उत्तर में दिए हैं)।