SQL सर्वर अप्रत्याशित चुनिंदा परिणाम (dbms त्रुटि?)


37

नीचे सरल उदाहरण है, जो अजीब परिणाम देता है, जो अप्रत्याशित हैं और हम इसे अपनी टीम में नहीं समझा सकते हैं। क्या हम कुछ गलत कर रहे हैं या यह SQL सर्वर त्रुटि है?

कुछ जांच के बाद हमने उप- क्षेत्र में संघ के खंड को खोज क्षेत्र को कम कर दिया , जो "पुरुष" तालिका से एक रिकॉर्ड का चयन करता है

यह SQL Server 2000 (12 पंक्तियों को लौटाता है) में अपेक्षित रूप से काम करता है, लेकिन 2008 और 2012 में यह केवल एक पंक्ति देता है।

create table dual (dummy int)

insert into dual values (0)

create table men (
man_id int,
wife_id int )

-- there are 12 men, 6 married 
insert into men values (1, 1)
insert into men values (2, 2)
insert into men values (3, null)
insert into men values (4, null)
insert into men values (5, null)
insert into men values (6, 3)
insert into men values (7, 5)
insert into men values (8, 7)
insert into men values (9, null)
insert into men values (10, null)
insert into men values (11, null)
insert into men values (12, 9)

यह केवल एक पंक्ति देता है: 1 1 2

select 
man_id,
wife_id,
(select count( * ) from 
    (select dummy from dual
     union select men.wife_id  ) family_members
) as family_size
from men
--where wife_id = 2 -- uncomment me and try again

Uncomment अंतिम पंक्ति और यह देता है: 2 2 2

बहुत ही अजीब व्यवहार है:

  • ड्रॉप्स, सीरीज़, ट्रंकट्स और इंसर्ट की श्रृंखला के बाद "मेन" टेबल पर यह कभी-कभी काम करता है (12 पंक्तियाँ लौटाता है)
  • जब आप "Union select men.wife_id" को "यूनियन सेलेक्ट करते हैं तो सभी select men.wife_id" या "Union select isnull (men.wife_id, null)" (!!!) में बदल जाते हैं, यह 12 पंक्तियाँ देता है (जैसा कि अपेक्षित है);
  • अजीब व्यवहार कॉलम "बीवी_ड" के डेटाटाइप से असंबंधित लगता है। हमने इसे बहुत अधिक डेटा सेट के साथ विकास प्रणाली पर मनाया।
  • "जहाँ wife_id> 0" 6 पंक्तियाँ देता है
  • हम इस तरह के बयानों के साथ विचारों के अजीब व्यवहार को भी देखते हैं। चयन करें * पंक्तियों का सबसेट लौटाता है, सभी शीर्ष 1000 का चयन करें

जवाबों:


35

क्या हम कुछ गलत कर रहे हैं या यह SQL सर्वर त्रुटि है?

यह एक गलत-परिणाम वाला बग है, जिसे आपको अपने सामान्य समर्थन चैनल के माध्यम से रिपोर्ट करना चाहिए। यदि आपके पास समर्थन अनुबंध नहीं है, तो यह जानने में मदद मिल सकती है कि Microsoft द्वारा बग के रूप में व्यवहार की पुष्टि करने पर भुगतान की गई घटनाएं सामान्य रूप से वापस हो जाती हैं।

बग को तीन सामग्रियों की आवश्यकता होती है:

  1. एक बाहरी संदर्भ के साथ नेस्टेड लूप (एक लागू)
  2. एक आंतरिक साइड आलसी सूचकांक स्पूल जो बाहरी संदर्भ पर तलाश करता है
  3. एक आंतरिक साइड कॉन्टैक्शन ऑपरेटर

उदाहरण के लिए, प्रश्न में प्रश्न निम्नलिखित की तरह एक योजना तैयार करता है:

अंकित योजना

इन तत्वों में से एक को हटाने के कई तरीके हैं, इसलिए बग अब पुन: पेश नहीं करता है।

उदाहरण के लिए, कोई अनुक्रमणिका या आँकड़े बना सकता है जिसका अर्थ होता है कि ऑप्टिमाइज़र एक आलसी सूचकांक स्पूल का उपयोग न करने का विकल्प चुनता है। या, एक संकेत का उपयोग करने के लिए हैश का उपयोग कर सकते हैं या संघ का उपयोग करने के बजाय संघ का विलय कर सकते हैं। एक ही शब्दार्थ को व्यक्त करने के लिए क्वेरी को फिर से लिखा जा सकता है, लेकिन जिसके परिणामस्वरूप एक अलग योजना आकार होता है जहां आवश्यक तत्वों में से एक या अधिक गायब होते हैं।

अधिक जानकारी

बाहरी संदर्भ (सहसंबद्ध पैरामीटर) मानों द्वारा अनुक्रमणित एक कार्य तालिका में एक आलसी सूचकांक स्पूल आलसी आंतरिक पक्ष परिणाम पंक्तियाँ देता है। यदि किसी आलसी इंडेक्स स्पूल को एक बाहरी संदर्भ के लिए कहा जाता है जो उसने पहले देखा है, तो यह अपने काम की मेज ("रिवाइंड") से कैश्ड परिणाम पंक्ति प्राप्त करता है। यदि स्पूल को एक बाहरी संदर्भ मूल्य के लिए कहा जाता है, जो उसने पहले नहीं देखा है, तो यह वर्तमान बाहरी संदर्भ मूल्य के साथ अपना उपप्रकार चलाता है और परिणाम (एक "रिबंड") को कैश करता है। आलसी सूचकांक स्पूल पर विधेय की तलाश अपने काम की मेज के लिए कुंजी को इंगित करती है।

इस विशिष्ट योजना आकार में समस्या तब होती है जब स्पूल यह देखने के लिए जांचता है कि क्या एक नया बाहरी संदर्भ वही है जो उसने पहले देखा है। नेस्टेड लूप्स अपने बाहरी संदर्भों को सही ढंग से अपडेट करता है, और अपने आंतरिक इनपुट पर ऑपरेटरों को उनके PrepRecomputeइंटरफ़ेस विधियों के माध्यम से सूचित करता है । इस चेक के शुरू होने पर, आंतरिक पक्ष के संचालकों ने CParamBounds:FNeedToReloadसंपत्ति को यह देखने के लिए पढ़ा कि क्या बाहरी संदर्भ पिछली बार से बदल गया है। एक उदाहरण स्टैक ट्रेस नीचे दिखाया गया है:

CParamBounds: FNeedToReload

जब ऊपर दिखाया गया सबट्री मौजूद है, विशेष रूप से जहां कॉन्टेनेटेशन का उपयोग किया जाता है, तो कुछ गलत हो जाता है (शायद एक बाइवाल / बायफ / कॉपी प्रॉब्लम) बाइंडिंग के साथ ऐसा होता है जो CParamBounds:FNeedToReloadहमेशा झूठा लौटता है, चाहे बाहरी संदर्भ वास्तव में बदल गया हो या नहीं।

जब एक ही सबट्री मौजूद होती है, लेकिन एक मर्ज यूनियन या हैश यूनियन का उपयोग किया जाता है, तो यह आवश्यक संपत्ति प्रत्येक पुनरावृत्ति पर सही ढंग से सेट की जाती है, और लेज़ी इंडेक्स स्पूल हर बार उपयुक्त के रूप में रिवाइंड या रिवाइंड करता है। डिस्टिक्ट सॉर्ट और स्ट्रीम एग्रीगेट, वैसे, बिना किसी दोष के हैं। मेरा संदेह यह है कि मर्ज और हैश यूनियन पिछले मूल्य की एक प्रति बनाते हैं, जबकि कॉनटेनेशन एक संदर्भ का उपयोग करता है। दुर्भाग्य से SQL सर्वर स्रोत कोड तक पहुंच के बिना इसे सत्यापित करना असंभव है।

शुद्ध परिणाम यह है कि समस्याग्रस्त योजना के आकार में आलसी सूचकांक स्पूल हमेशा सोचता है कि यह पहले से ही वर्तमान बाहरी संदर्भ को देख चुका है, अपनी कार्य तालिका में मांग करके फिर से याद करता है, आम तौर पर कुछ भी नहीं पाता है, इसलिए उस बाहरी संदर्भ के लिए कोई भी पंक्ति वापस नहीं आती है। डिबगर में निष्पादन के माध्यम से आगे बढ़ते हुए, स्पूल केवल कभी अपनी RewindHelperविधि को निष्पादित करता है , और कभी भी इसकी ReloadHelperविधि (इस संदर्भ में पुनः लोड करें) नहीं करता है। यह निष्पादन योजना में स्पष्ट है क्योंकि स्पूल के तहत ऑपरेटरों के पास 'संख्याओं की संख्या = 1' है।

RewindHelper

बेशक, बाहरी अपवाद के लिए पहले आलसी सूचकांक स्पूल दिया गया है। यह हमेशा सबट्री को निष्पादित करता है और कार्य तालिका में परिणाम पंक्ति को कैश करता है। बाद के सभी पुनरावृत्तियों में परिणाम होता है, जो केवल एक पंक्ति (एकल कैश्ड पंक्ति) का उत्पादन करेगा जब वर्तमान पुनरावृत्ति में बाहरी संदर्भ के लिए पहली बार के समान मूल्य होता है।

तो, नेस्टेड लूप्स जॉइन के बाहरी तरफ दिए गए किसी भी इनपुट के लिए, क्वेरी कई पंक्तियों के रूप में वापस आ जाएगी क्योंकि पहली पंक्ति के डुप्लिकेट संसाधित हैं (साथ ही पहली पंक्ति के लिए एक कोर्स)।

डेमो

तालिका और नमूना डेटा:

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 पंक्ति समाकलन योजना

और, जैसा कि संकेत दिया गया है, 8 पंक्तियों का उत्पादन किया जाता है, c1 = 1बिल्कुल:

8 पंक्ति परिणाम

मैंने देखा कि आपने इस बग के लिए एक कनेक्ट आइटम खोला है, लेकिन वास्तव में उन मुद्दों की रिपोर्ट करने के लिए जगह नहीं है जो उत्पादन प्रभाव डाल रहे हैं। यदि ऐसा है, तो आपको वास्तव में Microsoft समर्थन से संपर्क करना चाहिए।


यह गलत-परिणाम बग कुछ चरण में तय किया गया था। यह अब 2012 के बाद से SQL सर्वर के किसी भी संस्करण पर मेरे लिए पुन: पेश नहीं करता है। यह SQL Server 2008 R2 SP3-GDR बिल्ड 10.50.6560.0 (X64) पर रीप्रो करता है।


-3

आप कथन से बिना उपश्रेणी का उपयोग क्यों करते हैं? मुझे लगता है कि 2005 और 2008 के सर्वर में अंतर हो सकता है। शायद आप स्पष्ट रूप से शामिल हो सकते हैं?

select 
m1.man_id,
m1.wife_id,
(select count( * ) from 
    (select dummy from dual
     union
     select m2.wife_id
     from men m2
     where m2.man_id = m1.man_id) family_members
) as family_size
from men m1

3
हां, यह काम करता है, लेकिन मेरे संस्करण को भी काम करना चाहिए। अमूर्त उदाहरण के ऊपर हमारे उत्पादन क्वेरी का बहुत सरल संस्करण है, जो बहुत अधिक समझ में आता है।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.