जब मैं चर को इनलाइन करता हूं तो SQL सर्वर एक बेहतर निष्पादन योजना का उपयोग क्यों करता है?


32

मेरे पास एक SQL क्वेरी है जिसे मैं अनुकूलित करने की कोशिश कर रहा हूं:

DECLARE @Id UNIQUEIDENTIFIER = 'cec094e5-b312-4b13-997a-c91a8c662962'

SELECT 
  Id,
  MIN(SomeTimestamp),
  MAX(SomeInt)
FROM dbo.MyTable
WHERE Id = @Id
  AND SomeBit = 1
GROUP BY Id

MyTable दो सूचकांक हैं:

CREATE NONCLUSTERED INDEX IX_MyTable_SomeTimestamp_Includes
ON dbo.MyTable (SomeTimestamp ASC)
INCLUDE(Id, SomeInt)

CREATE NONCLUSTERED INDEX IX_MyTable_Id_SomeBit_Includes
ON dbo.MyTable (Id, SomeBit)
INCLUDE (TotallyUnrelatedTimestamp)

जब मैं क्वेरी को बिल्कुल ऊपर लिखे अनुसार निष्पादित करता हूं, तो SQL सर्वर पहले सूचकांक को स्कैन करता है, जिसके परिणामस्वरूप 189,703 तार्किक रीड और 2-3 सेकंड की अवधि होती है।

जब मैं @Idचर को इनलाइन करता हूं और क्वेरी को फिर से निष्पादित करता हूं , तो SQL सर्वर दूसरा सूचकांक खोजता है, जिसके परिणामस्वरूप केवल 104 तार्किक रीड और 0.001 सेकंड की अवधि (मूल रूप से तत्काल) होती है।

मुझे चर की आवश्यकता है, लेकिन मैं चाहता हूं कि एसक्यूएल अच्छी योजना का उपयोग करे। एक अस्थायी समाधान के रूप में मैंने क्वेरी पर एक सूचकांक संकेत दिया है, और क्वेरी मूल रूप से तत्काल है। हालांकि, मैं संभव होने पर सूचकांक के संकेतों से दूर रहने की कोशिश करता हूं। मैं आमतौर पर यह मानता हूं कि यदि क्वेरी ऑप्टिमाइज़र अपना काम करने में असमर्थ है, तो कुछ ऐसा है जो मैं कर सकता हूं (या करना बंद कर रहा हूं) यह स्पष्ट रूप से बताए बिना कि यह क्या करना है।

इसलिए, जब मैं चर को इनलाइन करता हूं तो SQL सर्वर एक बेहतर योजना के साथ क्यों आता है?

जवाबों:


44

SQL सर्वर में, गैर-सम्मिलित विधेयकों के तीन सामान्य रूप हैं:

एक साथ शाब्दिक मूल्य:

SELECT COUNT(*) AS records
FROM   dbo.Users AS u
WHERE  u.Reputation = 1;

एक पैरामीटर के साथ :

CREATE PROCEDURE dbo.SomeProc(@Reputation INT)
AS
BEGIN
    SELECT COUNT(*) AS records
    FROM   dbo.Users AS u
    WHERE  u.Reputation = @Reputation;
END;

एक साथ स्थानीय चर :

DECLARE @Reputation INT = 1

SELECT COUNT(*) AS records
FROM   dbo.Users AS u
WHERE  u.Reputation = @Reputation;

परिणाम

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

जब आप एक पैरामीटर का उपयोग करते हैं , तो ऑप्टिमाइज़र उस पैरामीटर के लिए एक योजना बनाएगा (इसे पैरामीटर सूँघना कहा जाता है ), और फिर उस योजना का पुन: उपयोग करें, अनुपस्थित recompile संकेत, योजना कैश निष्कासन, आदि।

जब आप एक स्थानीय चर का उपयोग करते हैं , तो अनुकूलक ... कुछ के लिए एक योजना बनाता है ।

यदि आप इस क्वेरी को चलाने वाले थे:

DECLARE @Reputation INT = 1

SELECT COUNT(*) AS records
FROM   dbo.Users AS u
WHERE  u.Reputation = @Reputation;

योजना इस तरह दिखाई देगी:

पागल

और उस स्थानीय चर के लिए पंक्तियों की अनुमानित संख्या इस तरह दिखाई देगी:

पागल

भले ही क्वेरी 4,744,427 की गिनती देता है।

स्थानीय चर, अज्ञात होने के कारण, कार्डिनैलिटी आकलन के लिए हिस्टोग्राम के 'अच्छे' हिस्से का उपयोग नहीं करते हैं। वे घनत्व वेक्टर के आधार पर एक अनुमान का उपयोग करते हैं।

पागल

SELECT 5.280389E-05 * 7250739 AS [poo]

वह आपको दे देंगे 382.86722457471, जो अनुमान लगाने वाला है।

ये अज्ञात अनुमान आमतौर पर बहुत खराब अनुमान हैं, और अक्सर खराब योजनाओं और खराब सूचकांक विकल्पों को जन्म दे सकते हैं।

इसे ठीक कर रहे हैं?

आपके विकल्प आम तौर पर हैं:

  • भंगुर सूचकांक संकेत
  • संभावित रूप से महंगी recompile संकेत
  • परिमेय गतिशील SQL
  • एक संग्रहीत प्रक्रिया
  • वर्तमान सूचकांक में सुधार करें

आपके विकल्प विशेष रूप से हैं:

वर्तमान सूचकांक में सुधार का मतलब है इसे क्वेरी द्वारा आवश्यक सभी कॉलमों को कवर करना:

CREATE NONCLUSTERED INDEX IX_MyTable_Id_SomeBit_Includes
ON dbo.MyTable (Id, SomeBit)
INCLUDE (TotallyUnrelatedTimestamp, SomeTimestamp, SomeInt)
WITH (DROP_EXISTING = ON);

यह मानते हुए कि Idमान यथोचित रूप से चयनात्मक हैं, इससे आपको एक अच्छी योजना मिल जाएगी, और इसे 'स्पष्ट' डेटा एक्सेस विधि देकर ऑप्टिमाइज़र की मदद करेंगे।

अधिक पढ़ना

आप यहाँ पैरामीटर एम्बेडिंग के बारे में अधिक पढ़ सकते हैं:


12

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

CREATE INDEX GetMinSomeTimestamp ON dbo.MyTable (Id, SomeTimestamp) WHERE SomeBit = 1;
CREATE INDEX GetMaxSomeInt ON dbo.MyTable (Id, SomeInt) WHERE SomeBit = 1;

नीचे मेरा परीक्षण डेटा है। मैंने तालिका में 13 एम पंक्तियों को रखा और उनमें से आधे स्तंभ के '3A35EA17-CE7E-4637-8319-4C517B6E48CA'लिए एक मूल्य है Id

DROP TABLE IF EXISTS dbo.MyTable;

CREATE TABLE dbo.MyTable (
    Id uniqueidentifier,
    SomeTimestamp DATETIME2,
    SomeInt INT,
    SomeBit BIT,
    FILLER VARCHAR(100)
);

INSERT INTO dbo.MyTable WITH (TABLOCK)
SELECT NEWID(), CURRENT_TIMESTAMP, 0, 1, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

INSERT INTO dbo.MyTable WITH (TABLOCK)
SELECT '3A35EA17-CE7E-4637-8319-4C517B6E48CA', CURRENT_TIMESTAMP, 0, 1, REPLICATE('Z', 100)
FROM master..spt_values t1
CROSS JOIN master..spt_values t2;

यह क्वेरी पहली बार में थोड़ी अजीब लग सकती है:

DECLARE @Id UNIQUEIDENTIFIER = '3A35EA17-CE7E-4637-8319-4C517B6E48CA'

SELECT
  @Id,
  st.SomeTimestamp,
  si.SomeInt
FROM (
    SELECT TOP (1) SomeInt, Id
    FROM dbo.MyTable
    WHERE Id = @Id
    AND SomeBit = 1
    ORDER BY SomeInt DESC
) si
CROSS JOIN (
    SELECT TOP (1) SomeTimestamp, Id
    FROM dbo.MyTable
    WHERE Id = @Id
    AND SomeBit = 1
    ORDER BY SomeTimestamp ASC
) st;

यह कुछ तार्किक रीड के साथ न्यूनतम या अधिकतम मूल्य खोजने के लिए अनुक्रमित के आदेश का लाभ उठाने के लिए डिज़ाइन किया गया है। CROSS JOINवहाँ सही परिणाम प्राप्त करने के लिए जब वहाँ के लिए कोई मेल खाते पंक्तियों नहीं हैं @Idमूल्य। भले ही मैं तालिका में सबसे लोकप्रिय मूल्य (6.5 मिलियन पंक्तियों से मेल खाता) पर फ़िल्टर करता हूं, मुझे केवल 8 तार्किक रीड मिलते हैं:

तालिका 'MyTable'। स्कैन गिनती 2, तार्किक 8 पढ़ता है

यहाँ क्वेरी योजना है:

यहाँ छवि विवरण दर्ज करें

दोनों सूचकांक 0 या 1 पंक्तियों को ढूंढते हैं। यह बेहद कुशल है, लेकिन दो परिदृश्यों को बनाना आपके परिदृश्य के लिए भारी पड़ सकता है। आप इसके बजाय निम्नलिखित सूचकांक पर विचार कर सकते हैं:

CREATE INDEX CoveringIndex ON dbo.MyTable (Id) INCLUDE (SomeTimestamp, SomeInt) WHERE SomeBit = 1;

अब मूल क्वेरी (वैकल्पिक MAXDOP 1संकेत के साथ) के लिए क्वेरी योजना कुछ अलग दिखती है:

यहाँ छवि विवरण दर्ज करें

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

तालिका 'MyTable'। स्कैन गिनती 1, तार्किक 33757 पढ़ता है


2

मैं इसका उत्तर यहां क्यों नहीं दे सकता , लेकिन यह सुनिश्चित करने का त्वरित और गंदा तरीका है कि क्वेरी जिस तरह से आप चाहते हैं वह चलती है:

DECLARE @Id UNIQUEIDENTIFIER = 'cec094e5-b312-4b13-997a-c91a8c662962'
SELECT 
  Id,
  MIN(SomeTimestamp),
  MAX(SomeInt)
FROM dbo.MyTable WITH (INDEX(IX_MyTable_Id_SomeBit_Includes))
WHERE Id = @Id
  AND SomeBit = 1
GROUP BY Id

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.