SQL सर्वर में कई-से-कई जुड़ने का संकेत कैसे दें?


9

मेरे पास 3 "बड़े" टेबल हैं जो कॉलम (दोनों intएस) की एक जोड़ी पर जुड़ते हैं ।

  • तालिका 1 में ~ 200 मिलियन पंक्तियाँ हैं
  • Table2 में ~ 1.5 मिलियन पंक्तियाँ हैं
  • तालिका 3 में ~ 6 मिलियन पंक्तियाँ हैं

प्रत्येक मेज पर एक संकुल अनुक्रमणिका है 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(यह काफी मदद करता है, लेकिन मैं डेटाबेस में हजारों उपयोगकर्ता-निर्मित आँकड़ों के साथ समाप्त होता हूं।)

नकाबपोश निष्पादन योजना (खराब मास्किंग के लिए खेद है)

जिस मामले में मैं देख रहा हूं, उसके परिणाम में 9 मिलियन पंक्तियाँ हैं। नए CE 180 पंक्तियों का अनुमान लगाता है; विरासत सीई 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;

जवाबों:


5

बस स्पष्ट होने के लिए, ऑप्टिमाइज़र को पहले से ही पता है कि यह कई-से-कई में शामिल है। यदि आप मर्ज को जोड़ते हैं और एक अनुमानित योजना को देखते हैं तो आप जॉइन ऑपरेटर के लिए एक प्रॉपर्टी देख सकते हैं जो आपको बताती है कि क्या जॉइन कई-से-कई हो सकते हैं। यहां आपको जिस समस्या को हल करने की आवश्यकता है, वह कार्डिनैलिटी के अनुमानों से टकरा रही है, संभवत: इसलिए आपके द्वारा छोड़े गए क्वेरी के भाग के लिए एक अधिक कुशल क्वेरी योजना मिलती है।

पहली चीज जो मैं करने की कोशिश करूंगा उसमें शामिल होने के परिणामों को एक अस्थायी तालिका से Object3और अंदर Object5डाल रहा है। उस योजना के लिए जिसे आपने 51393 पंक्तियों में एक एकल स्तंभ पोस्ट किया है, इसलिए इसे टेम्पर्डब में किसी स्थान को शायद ही लेना चाहिए। आप अस्थायी तालिका पर पूर्ण आँकड़े एकत्र कर सकते हैं और अकेले ही एक पर्याप्त सटीक अंतिम कार्डिनैलिटी अनुमान प्राप्त करने के लिए पर्याप्त हो सकते हैं। पूर्ण आँकड़ों को इकट्ठा करने से Object1भी मदद मिल सकती है। कार्डिनलिटी का अनुमान अक्सर खराब हो जाता है क्योंकि आप एक योजना से दाएं से बाएं की ओर बढ़ते हैं।

यदि वह काम नहीं करता है ENABLE_QUERY_OPTIMIZER_HOTFIXESतो आप क्वेरी संकेत की कोशिश कर सकते हैं यदि आपके पास पहले से ही यह डेटाबेस या सर्वर स्तर पर सक्षम नहीं है। Microsoft उस सेटिंग के पीछे SQL सर्वर 2016 के लिए योजना-प्रभावित प्रदर्शन सुधारों को लॉक करता है। उनमें से कुछ कार्डिनैलिटी अनुमानों से संबंधित हैं, इसलिए शायद आप भाग्यशाली होंगे और फिक्स में से एक आपकी क्वेरी के साथ मदद करेगा। आप FORCE_LEGACY_CARDINALITY_ESTIMATIONक्वेरी हिंट के साथ लीगेसी कार्डिनैलिटी अनुमानक का उपयोग करने का भी प्रयास कर सकते हैं । कुछ डेटा सेटों को विरासत सीई के साथ बेहतर अनुमान मिल सकता है।

एक अंतिम उपाय के रूप में आप आदम मैकानिक के MANY()फ़ंक्शन का उपयोग करके जो भी कारक पसंद करते हैं, वह कार्डिनैलिटी अनुमान को मैन्युअल रूप से बढ़ा सकते हैं । मैं इसके बारे में एक और जवाब में बात करता हूं लेकिन ऐसा लगता है कि लिंक मर चुका है। यदि आप रुचि रखते हैं तो मैं कुछ खोदने की कोशिश कर सकता हूं।


एडम का make_parallelकार्य समस्या को कम करने में मदद करने के लिए उपयोग किया जाता है। मैं देख लूंगा many। एक सुंदर सकल बैंड-सहायता की तरह लगता है।
स्टीवन हिबल

2

SQL सर्वर आँकड़े केवल आँकड़े ऑब्जेक्ट के प्रमुख स्तंभ के लिए एक हिस्टोग्राम होते हैं। इसलिए, आप फ़िल्टर किए गए आँकड़े बना सकते हैं, जो Key2केवल पंक्तियों के बीच मानों का एक हिस्टोग्राम प्रदान करते हैं Key1 = 1। प्रत्येक तालिका पर इन फ़िल्टर्ड आँकड़ों का निर्माण अनुमानों को ठीक करता है और परीक्षण क्वेरी के लिए आपके द्वारा अपेक्षित व्यवहार की ओर जाता है: प्रत्येक नई जॉइन अंतिम कार्डिनैलिटी अनुमान (SQL 2016 SP1 और SQL 2017 दोनों में पुष्टि की गई) को प्रभावित नहीं करती है।

-- Note: Add "WITH FULLSCAN" to each if you want a perfect 20,000 row estimate
CREATE STATISTICS st_#Table1 ON #Table1 (Key2) WHERE Key1 = 1
CREATE STATISTICS st_#Table2 ON #Table2 (Key2) WHERE Key1 = 1
CREATE STATISTICS st_#Table3 ON #Table3 (Key2) WHERE Key1 = 1

इन फ़िल्टर किए गए आँकड़ों के बिना, SQL सर्वर आपके जुड़ने की कार्डिनैलिटी का आकलन करने के लिए अधिक आधार-आधारित दृष्टिकोण लेगा। निम्न श्वेतपत्र में SQL सर्वर का उपयोग करने वाले कुछ ह्यूरिस्टिक्स के अच्छे उच्च-स्तरीय विवरण हैं: SQL Server 2014 कार्डिनैलिटी एस्टीमेटर के साथ आपका क्वेरी प्लान ऑप्टिमाइज़ करना

उदाहरण के लिए, USE HINT('ASSUME_JOIN_PREDICATE_DEPENDS_ON_FILTERS')अपनी क्वेरी में संकेत जोड़ने के लिए Key1विधेय और स्वतंत्रता के बीच कुछ सह-संबंध (स्वतंत्रता के बजाय) के साथ Key2जुड़ने वाले उत्तराधिकार को जोड़ देंगे, जो आपकी क्वेरी के लिए फायदेमंद हो सकता है। अंतिम परीक्षण क्वेरी के लिए, यह संकेत से कार्डिनैलिटी अनुमान को बढ़ाता 1,175है 7,551, लेकिन 20,000फ़िल्टर्ड आँकड़ों के साथ उत्पन्न सही पंक्ति अनुमान से अभी भी थोड़ा शर्मीला है ।

इसी तरह की स्थितियों में हमारे द्वारा उपयोग किया गया एक और दृष्टिकोण #temp तालिकाओं में डेटा के संबंधित सबसेट को निकालना है। विशेष रूप से अब जब SQL सर्वर के नए संस्करण अब उत्सुकता से डिस्क पर #temp टेबल नहीं लिखते हैं , तो हमारे पास इस दृष्टिकोण के साथ अच्छे परिणाम हैं। आपके कई-से-बहुत से जुड़ने का विवरण बताता है कि आपके मामले में प्रत्येक व्यक्तिगत #temp तालिका अपेक्षाकृत छोटी होगी (या अंतिम परिणाम सेट की तुलना में कम से कम छोटी), इसलिए यह दृष्टिकोण प्रयास करने लायक हो सकता है।

DROP TABLE IF EXISTS #Table1_extract, #Table2_extract, #Table3_extract, #c
-- Extract only the subset of rows that match the filter predicate
-- (Or better yet, extract only the subset of columns you need!)
SELECT * INTO #Table1_extract FROM #Table1 WHERE Key1 = 1
SELECT * INTO #Table2_extract FROM #Table2 WHERE Key1 = 1
SELECT * INTO #Table3_extract FROM #Table3 WHERE Key1 = 1
-- Now perform the join on those extracts, removing the filter predicate
SELECT col = 1
INTO #c 
FROM #Table1_extract t1
JOIN #Table2_extract t2
    ON t1.Key2 = t2.Key2
JOIN #Table3_extract t3
    ON t1.Key2 = t3.Key2

हम फ़िल्टर्ड आँकड़ों का बड़े पैमाने पर उपयोग करते हैं, लेकिन हम उन्हें Key1प्रत्येक तालिका पर एक मूल्य प्रति बनाते हैं । अब हमारे पास उनके हजारों हैं।
स्टीवन हिबल

2
@StevenHibble अच्छा मुद्दा है कि हजारों फ़िल्टर किए गए आँकड़े प्रबंधन को मुश्किल बना सकते हैं। (हमने यह भी देखा है कि यह योजना संकलन समय को नकारात्मक रूप से प्रभावित करता है।) यह आपके उपयोग के मामले में फिट नहीं हो सकता है, लेकिन मैंने एक और #temp तालिका दृष्टिकोण भी जोड़ा है जिसे हमने कई बार सफलतापूर्वक उपयोग किया है।
ज्योफ पैटरसन

-1

एक पहुंच। कोशिश के अलावा कोई वास्तविक आधार नहीं है।

SELECT 1
FROM Table1 t1
     JOIN Table2 t2
       ON t1.Key2 = t2.Key2
      AND t1.Key1 = 1
      AND t2.Key1 = 1
     JOIN Table3 t3
       ON t2.Key2 = t3.Key2
      AND t3.Key1 = 1;
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.