हम में से कई ऐसी विशेषताएं हैं जिनसे हम कतराते हैं, जैसे कर्सर, ट्रिगर और डायनेमिक SQL। इसमें कोई संदेह नहीं है कि उनमें से प्रत्येक के पास अपने उपयोग के मामले हैं, लेकिन जब हम गतिशील एसक्यूएल के अंदर एक कर्सर के साथ एक ट्रिगर देखते हैं, तो यह हमें परेशान कर सकता है (ट्रिपल व्हैमी)।
योजना मार्गदर्शिकाएँ और sp_prepare एक समान नाव में हैं:यदि आपने मुझे उनमें से किसी एक का उपयोग करते हुए देखा, तो आप भौंहें उठाएँगे; यदि आपने मुझे उनका एक साथ उपयोग करते हुए देखा, तो आप शायद मेरा तापमान जांच लेंगे। लेकिन, कर्सर, ट्रिगर और डायनेमिक SQL की तरह, उनके पास उनके उपयोग के मामले हैं। और मुझे हाल ही में एक ऐसा परिदृश्य मिला, जहां उनका एक साथ उपयोग करना फायदेमंद था।
पृष्ठभूमि
हमारे पास बहुत सारा डेटा है। और बहुत सारे एप्लिकेशन उस डेटा के खिलाफ चल रहे हैं। उन अनुप्रयोगों में से कुछ को बदलना मुश्किल या असंभव है, विशेष रूप से किसी तीसरे पक्ष के ऑफ-द-शेल्फ एप्लिकेशन। इसलिए जब उनका संकलित एप्लिकेशन SQL सर्वर को विशेष रूप से तैयार कथन के रूप में तदर्थ प्रश्न भेजता है, और जब हमें अनुक्रमणिका जोड़ने या बदलने की स्वतंत्रता नहीं होती है, तो कई ट्यूनिंग अवसर तुरंत तालिका से बाहर हो जाते हैं।
इस मामले में, हमारे पास कुछ मिलियन पंक्तियों वाली एक तालिका थी। एक सरलीकृत और स्वच्छ संस्करण:
CREATE TABLE dbo.TheThings ( ThingID bigint NOT NULL, TypeID uniqueidentifier NOT NULL, dt1 datetime NOT NULL DEFAULT sysutcdatetime(), dt2 datetime NOT NULL DEFAULT sysutcdatetime(), dt3 datetime NOT NULL DEFAULT sysutcdatetime(), CONSTRAINT PK_TheThings PRIMARY KEY (ThingID) ); CREATE INDEX ix_type ON dbo.TheThings(TypeID); SET NOCOUNT ON; GO DECLARE @guid1 uniqueidentifier = 'EE81197A-B2EA-41F4-882E-4A5979ACACE4', @guid2 uniqueidentifier = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; INSERT dbo.TheThings(ThingID, TypeID) SELECT TOP (1000) 1000 + ROW_NUMBER() OVER (ORDER BY name), @guid1 FROM sys.all_columns; INSERT dbo.TheThings(ThingID, TypeID) SELECT TOP (1) 2500, @guid2 FROM sys.all_columns; INSERT dbo.TheThings(ThingID, TypeID) SELECT TOP (1000) 3000 + ROW_NUMBER() OVER (ORDER BY name), @guid1 FROM sys.all_columns;
आवेदन से तैयार विवरण इस तरह दिखता है (जैसा कि योजना कैश में देखा गया है):
(@P0 varchar(8000))SELECT * FROM dbo.TheThings WHERE TypeID = @P0
समस्या यह है कि, TypeID
. के कुछ मानों के लिए , कई हज़ार पंक्तियाँ होंगी। अन्य मानों के लिए, 10 से कम होगा। यदि एक पैरामीटर प्रकार के आधार पर गलत योजना का चयन (और पुन:उपयोग) किया जाता है, तो यह दूसरों के लिए परेशानी का सबब बन सकता है। कुछ पंक्तियों को पुनर्प्राप्त करने वाली क्वेरी के लिए, हम अतिरिक्त गैर-कवर किए गए कॉलम को पुनर्प्राप्त करने के लिए लुकअप के साथ एक इंडेक्स खोज चाहते हैं, लेकिन 700K पंक्तियों को वापस करने वाली क्वेरी के लिए, हम केवल क्लस्टर इंडेक्स स्कैन चाहते हैं। (आदर्श रूप से, इंडेक्स कवर होगा, लेकिन यह विकल्प इस बार कार्ड में नहीं था।)
व्यवहार में, एप्लिकेशन को हमेशा स्कैन भिन्नता मिल रही थी, भले ही उस समय की लगभग 1% आवश्यकता थी। 99% क्वेरीज़ 2 मिलियन रो स्कैन का उपयोग कर रही थीं, जब वे एक सीक + 4 या 5 लुकअप का उपयोग कर सकते थे।
हम इस क्वेरी को चलाकर प्रबंधन स्टूडियो में इसे आसानी से पुन:पेश कर सकते हैं:
DBCC FREEPROCCACHE; DECLARE @P0 uniqueidentifier = 'EE81197A-B2EA-41F4-882E-4A5979ACACE4'; SELECT * FROM dbo.TheThings WHERE TypeID = @P0; GO DBCC FREEPROCCACHE; DECLARE @P0 uniqueidentifier = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; SELECT * FROM dbo.TheThings WHERE TypeID = @P0; GO
योजनाएँ इस तरह वापस आईं:
दोनों मामलों में अनुमान 1,000 पंक्तियों का था; दायीं ओर की चेतावनियां शेष I/O के कारण हैं।
हम कैसे सुनिश्चित कर सकते हैं कि पैरामीटर के आधार पर क्वेरी ने सही चुनाव किया है? हमें क्वेरी में संकेत जोड़े बिना, ट्रेस फ़्लैग चालू किए, या डेटाबेस सेटिंग बदले बिना इसे फिर से संकलित करना होगा।
अगर मैंने OPTION (RECOMPILE)
. का उपयोग करके स्वतंत्र रूप से प्रश्नों को चलाया है , उपयुक्त होने पर मुझे तलाश मिल जाएगी:
DBCC FREEPROCCACHE; DECLARE @guid1 uniqueidentifier = 'EE81197A-B2EA-41F4-882E-4A5979ACACE4', @guid2 uniqueidentifier = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; SELECT * FROM dbo.TheThings WHERE TypeID = @guid1 OPTION (RECOMPILE); SELECT * FROM dbo.TheThings WHERE TypeID = @guid2 OPTION (RECOMPILE);
RECOMPILE के साथ, हमें अधिक सटीक अनुमान मिलते हैं, और जब हमें इसकी आवश्यकता होती है तो एक तलाश करते हैं।
लेकिन, फिर से, हम सीधे क्वेरी में संकेत नहीं जोड़ सके।
आइए एक योजना मार्गदर्शिका देखें
बहुत से लोग योजना गाइडों के खिलाफ चेतावनी देते हैं, लेकिन हम यहाँ एक कोने में थे। यदि हम कर सकते हैं तो हम निश्चित रूप से क्वेरी, या अनुक्रमणिका को बदलना पसंद करेंगे। लेकिन यह अगली सबसे अच्छी बात हो सकती है।
EXEC sys.sp_create_plan_guide @name = N'TheThingGuide', @stmt = N'SELECT * FROM dbo.TheThings WHERE TypeID = @P0', @type = N'SQL', @params = N'@P0 varchar(8000)', @hints = N'OPTION (RECOMPILE)';
सीधा सा लगता है; इसका परीक्षण समस्या है। हम प्रबंधन स्टूडियो में तैयार किए गए कथन का अनुकरण कैसे करते हैं? हम कैसे सुनिश्चित कर सकते हैं कि एप्लिकेशन को निर्देशित योजना मिल रही है, और यह स्पष्ट रूप से योजना मार्गदर्शिका के कारण है?
यदि हम इस क्वेरी को SSMS में अनुकरण करने का प्रयास करते हैं, तो इसे एक तदर्थ कथन के रूप में माना जाता है, न कि तैयार किए गए कथन के रूप में, और मुझे योजना मार्गदर्शिका लेने के लिए यह नहीं मिला:
DECLARE @P0 varchar(8000) = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; -- also tried uniqueidentifier SELECT * FROM dbo.TheThings WHERE TypeID = @P0
डायनामिक एसक्यूएल भी काम नहीं कर रहा था (इसे एक एड हॉक स्टेटमेंट के रूप में भी माना जाता है):
DECLARE @sql nvarchar(max) = N'SELECT * FROM dbo.TheThings WHERE TypeID = @P0', @params nvarchar(max) = N'@P0 varchar(8000)', -- also tried uniqueidentifier @P0 varchar(8000) = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; EXEC sys.sp_executesql @sql, @params, @P0;
और मैं ऐसा नहीं कर सकता था, क्योंकि यह योजना गाइड को भी नहीं उठाएगा (पैरामीटरीकरण यहां होता है, और मुझे डेटाबेस सेटिंग्स को बदलने की स्वतंत्रता नहीं थी, भले ही इसे एक तैयार बयान की तरह माना जाए) :
SELECT * FROM TheThings WHERE TypeID = 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F';
मैं ऐप से चल रहे प्रश्नों के लिए योजना कैश की जांच नहीं कर सकता, क्योंकि कैश्ड योजना योजना गाइड उपयोग के बारे में कुछ भी इंगित नहीं करती है (जब आप वास्तविक योजना बनाते हैं तो एसएसएमएस आपके लिए एक्सएमएल में उस जानकारी को इंजेक्ट करता है)। और अगर क्वेरी वास्तव में RECOMPILE संकेत का पालन कर रही है, तो मैं योजना मार्गदर्शिका में जा रहा हूं, फिर भी मैं कभी भी योजना कैश में कोई सबूत कैसे देख सकता हूं?
चलो sp_prepare करने का प्रयास करें
मैंने अपने करियर में योजना गाइडों की तुलना में कम sp_prepare का उपयोग किया है, और मैं इसे एप्लिकेशन कोड के लिए उपयोग करने की अनुशंसा नहीं करता। (जैसा कि एरिक डार्लिंग बताते हैं, अनुमान घनत्व वेक्टर से खींचा जा सकता है, पैरामीटर को सूँघने से नहीं।)
मेरे मामले में, मैं इसे प्रदर्शन कारणों से उपयोग नहीं करना चाहता, मैं ऐप से आने वाले तैयार कथन को अनुकरण करने के लिए इसका उपयोग (sp_execute के साथ) करना चाहता हूं।
DECLARE @o int; EXEC sys.sp_prepare @o OUTPUT, N'@P0 varchar(8000)', N'SELECT * FROM dbo.TheThings WHERE TypeID = @P0'; EXEC sys.sp_execute @o, 'EE81197A-B2EA-41F4-882E-4A5979ACACE4'; -- PK scan EXEC sys.sp_execute @o, 'D989AADB-5C34-4EE1-9BE2-A88B8F74A23F'; -- IX seek + lookup
SSMS हमें दिखाता है कि दोनों मामलों में योजना गाइड का उपयोग किया गया था।
आप इन परिणामों के लिए योजना कैश की जांच नहीं कर पाएंगे, क्योंकि पुनर्संकलन। लेकिन मेरे जैसे परिदृश्य में, आपको निगरानी में प्रभावों को देखने में सक्षम होना चाहिए, विस्तारित घटनाओं के माध्यम से स्पष्ट रूप से जांच करना, या उस लक्षण की राहत को देखकर जिसने आपको इस क्वेरी की पहली जगह में जांच की (केवल औसत रनटाइम, क्वेरी से अवगत रहें आँकड़े आदि अतिरिक्त संकलन से प्रभावित हो सकते हैं)।
निष्कर्ष
यह एक ऐसा मामला था जहां एक योजना गाइड फायदेमंद थी, और sp_prepare यह सत्यापित करने में उपयोगी था कि यह आवेदन के लिए काम करेगा। ये अक्सर उपयोगी नहीं होते हैं, और कम अक्सर एक साथ होते हैं, लेकिन मेरे लिए यह एक दिलचस्प संयोजन था। योजना मार्गदर्शिका के बिना भी, यदि आप तैयार विवरण भेजने वाले ऐप को अनुकरण करने के लिए एसएसएमएस का उपयोग करना चाहते हैं, तो sp_prepare आपका मित्र है। (sp_prepexec भी देखें, जो एक शॉर्टकट हो सकता है यदि आप एक ही क्वेरी के लिए दो अलग-अलग योजनाओं को सत्यापित करने का प्रयास नहीं कर रहे हैं।)
ध्यान दें कि यह अभ्यास हर समय बेहतर प्रदर्शन प्राप्त करने के लिए जरूरी नहीं था - यह प्रदर्शन भिन्नता को समतल करने के लिए था। रीकंपाइल्स स्पष्ट रूप से मुक्त नहीं हैं, लेकिन मैं 99% प्रश्नों के लिए बिल्कुल भयानक योजना के साथ फंसने के बजाय, मेरे 99% प्रश्नों को 250ms में निष्पादित करने और 5 सेकंड में 1% निष्पादित करने के लिए एक छोटा सा जुर्माना चुकाऊंगा। या 1% क्वेरीज़।