क्या हम कुछ गलत कर रहे हैं या यह SQL सर्वर त्रुटि है?
यह एक गलत-परिणाम वाला बग है, जिसे आपको अपने सामान्य समर्थन चैनल के माध्यम से रिपोर्ट करना चाहिए। यदि आपके पास समर्थन अनुबंध नहीं है, तो यह जानने में मदद मिल सकती है कि Microsoft द्वारा बग के रूप में व्यवहार की पुष्टि करने पर भुगतान की गई घटनाएं सामान्य रूप से वापस हो जाती हैं।
बग को तीन सामग्रियों की आवश्यकता होती है:
- एक बाहरी संदर्भ के साथ नेस्टेड लूप (एक लागू)
- एक आंतरिक साइड आलसी सूचकांक स्पूल जो बाहरी संदर्भ पर तलाश करता है
- एक आंतरिक साइड कॉन्टैक्शन ऑपरेटर
उदाहरण के लिए, प्रश्न में प्रश्न निम्नलिखित की तरह एक योजना तैयार करता है:
इन तत्वों में से एक को हटाने के कई तरीके हैं, इसलिए बग अब पुन: पेश नहीं करता है।
उदाहरण के लिए, कोई अनुक्रमणिका या आँकड़े बना सकता है जिसका अर्थ होता है कि ऑप्टिमाइज़र एक आलसी सूचकांक स्पूल का उपयोग न करने का विकल्प चुनता है। या, एक संकेत का उपयोग करने के लिए हैश का उपयोग कर सकते हैं या संघ का उपयोग करने के बजाय संघ का विलय कर सकते हैं। एक ही शब्दार्थ को व्यक्त करने के लिए क्वेरी को फिर से लिखा जा सकता है, लेकिन जिसके परिणामस्वरूप एक अलग योजना आकार होता है जहां आवश्यक तत्वों में से एक या अधिक गायब होते हैं।
अधिक जानकारी
बाहरी संदर्भ (सहसंबद्ध पैरामीटर) मानों द्वारा अनुक्रमणित एक कार्य तालिका में एक आलसी सूचकांक स्पूल आलसी आंतरिक पक्ष परिणाम पंक्तियाँ देता है। यदि किसी आलसी इंडेक्स स्पूल को एक बाहरी संदर्भ के लिए कहा जाता है जो उसने पहले देखा है, तो यह अपने काम की मेज ("रिवाइंड") से कैश्ड परिणाम पंक्ति प्राप्त करता है। यदि स्पूल को एक बाहरी संदर्भ मूल्य के लिए कहा जाता है, जो उसने पहले नहीं देखा है, तो यह वर्तमान बाहरी संदर्भ मूल्य के साथ अपना उपप्रकार चलाता है और परिणाम (एक "रिबंड") को कैश करता है। आलसी सूचकांक स्पूल पर विधेय की तलाश अपने काम की मेज के लिए कुंजी को इंगित करती है।
इस विशिष्ट योजना आकार में समस्या तब होती है जब स्पूल यह देखने के लिए जांचता है कि क्या एक नया बाहरी संदर्भ वही है जो उसने पहले देखा है। नेस्टेड लूप्स अपने बाहरी संदर्भों को सही ढंग से अपडेट करता है, और अपने आंतरिक इनपुट पर ऑपरेटरों को उनके PrepRecompute
इंटरफ़ेस विधियों के माध्यम से सूचित करता है । इस चेक के शुरू होने पर, आंतरिक पक्ष के संचालकों ने CParamBounds:FNeedToReload
संपत्ति को यह देखने के लिए पढ़ा कि क्या बाहरी संदर्भ पिछली बार से बदल गया है। एक उदाहरण स्टैक ट्रेस नीचे दिखाया गया है:
जब ऊपर दिखाया गया सबट्री मौजूद है, विशेष रूप से जहां कॉन्टेनेटेशन का उपयोग किया जाता है, तो कुछ गलत हो जाता है (शायद एक बाइवाल / बायफ / कॉपी प्रॉब्लम) बाइंडिंग के साथ ऐसा होता है जो CParamBounds:FNeedToReload
हमेशा झूठा लौटता है, चाहे बाहरी संदर्भ वास्तव में बदल गया हो या नहीं।
जब एक ही सबट्री मौजूद होती है, लेकिन एक मर्ज यूनियन या हैश यूनियन का उपयोग किया जाता है, तो यह आवश्यक संपत्ति प्रत्येक पुनरावृत्ति पर सही ढंग से सेट की जाती है, और लेज़ी इंडेक्स स्पूल हर बार उपयुक्त के रूप में रिवाइंड या रिवाइंड करता है। डिस्टिक्ट सॉर्ट और स्ट्रीम एग्रीगेट, वैसे, बिना किसी दोष के हैं। मेरा संदेह यह है कि मर्ज और हैश यूनियन पिछले मूल्य की एक प्रति बनाते हैं, जबकि कॉनटेनेशन एक संदर्भ का उपयोग करता है। दुर्भाग्य से SQL सर्वर स्रोत कोड तक पहुंच के बिना इसे सत्यापित करना असंभव है।
शुद्ध परिणाम यह है कि समस्याग्रस्त योजना के आकार में आलसी सूचकांक स्पूल हमेशा सोचता है कि यह पहले से ही वर्तमान बाहरी संदर्भ को देख चुका है, अपनी कार्य तालिका में मांग करके फिर से याद करता है, आम तौर पर कुछ भी नहीं पाता है, इसलिए उस बाहरी संदर्भ के लिए कोई भी पंक्ति वापस नहीं आती है। डिबगर में निष्पादन के माध्यम से आगे बढ़ते हुए, स्पूल केवल कभी अपनी RewindHelper
विधि को निष्पादित करता है , और कभी भी इसकी ReloadHelper
विधि (इस संदर्भ में पुनः लोड करें) नहीं करता है। यह निष्पादन योजना में स्पष्ट है क्योंकि स्पूल के तहत ऑपरेटरों के पास 'संख्याओं की संख्या = 1' है।
बेशक, बाहरी अपवाद के लिए पहले आलसी सूचकांक स्पूल दिया गया है। यह हमेशा सबट्री को निष्पादित करता है और कार्य तालिका में परिणाम पंक्ति को कैश करता है। बाद के सभी पुनरावृत्तियों में परिणाम होता है, जो केवल एक पंक्ति (एकल कैश्ड पंक्ति) का उत्पादन करेगा जब वर्तमान पुनरावृत्ति में बाहरी संदर्भ के लिए पहली बार के समान मूल्य होता है।
तो, नेस्टेड लूप्स जॉइन के बाहरी तरफ दिए गए किसी भी इनपुट के लिए, क्वेरी कई पंक्तियों के रूप में वापस आ जाएगी क्योंकि पहली पंक्ति के डुप्लिकेट संसाधित हैं (साथ ही पहली पंक्ति के लिए एक कोर्स)।
डेमो
तालिका और नमूना डेटा:
CREATE TABLE #T1
(
pk integer IDENTITY NOT NULL,
c1 integer NOT NULL,
CONSTRAINT PK_T1
PRIMARY KEY CLUSTERED (pk)
);
GO
INSERT #T1 (c1)
VALUES
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6);
निम्नलिखित (तुच्छ) क्वेरी मर्ज यूनियन का उपयोग करके प्रत्येक पंक्ति के लिए दो की कुल संख्या (कुल मिलाकर 18) की एक सही गणना पैदा करती है:
SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY
(
SELECT COUNT_BIG(*) AS c1
FROM
(
SELECT T1.c1
UNION
SELECT NULL
) AS U
) AS C;
यदि हम अब एक अवतरण बल के लिए एक क्वेरी संकेत जोड़ते हैं:
SELECT T1.c1, C.c1
FROM #T1 AS T1
CROSS APPLY
(
SELECT COUNT_BIG(*) AS c1
FROM
(
SELECT T1.c1
UNION
SELECT NULL
) AS U
) AS C
OPTION (CONCAT UNION);
निष्पादन योजना में समस्याग्रस्त आकार है:
और परिणाम अब गलत है, सिर्फ तीन पंक्तियाँ:
हालांकि इस व्यवहार की गारंटी नहीं है, क्लस्टर्ड इंडेक्स स्कैन से पहली पंक्ति का c1
मूल्य 1 है। इस मान के साथ दो अन्य पंक्तियाँ हैं, इसलिए तीन पंक्तियों का कुल उत्पादन होता है।
अब डेटा तालिका को काटें और इसे 'पहले' पंक्ति के अधिक डुप्लिकेट के साथ लोड करें:
TRUNCATE TABLE #T1;
INSERT #T1 (c1)
VALUES
(1), (2), (3), (4), (5), (6),
(1), (2), (3), (4), (5), (6),
(1), (1), (1), (1), (1), (1);
अब सम्मिलन योजना है:
और, जैसा कि संकेत दिया गया है, 8 पंक्तियों का उत्पादन किया जाता है, c1 = 1
बिल्कुल:
मैंने देखा कि आपने इस बग के लिए एक कनेक्ट आइटम खोला है, लेकिन वास्तव में उन मुद्दों की रिपोर्ट करने के लिए जगह नहीं है जो उत्पादन प्रभाव डाल रहे हैं। यदि ऐसा है, तो आपको वास्तव में Microsoft समर्थन से संपर्क करना चाहिए।
यह गलत-परिणाम बग कुछ चरण में तय किया गया था। यह अब 2012 के बाद से SQL सर्वर के किसी भी संस्करण पर मेरे लिए पुन: पेश नहीं करता है। यह SQL Server 2008 R2 SP3-GDR बिल्ड 10.50.6560.0 (X64) पर रीप्रो करता है।