नोट:यह पोस्ट मूल रूप से केवल हमारी ईबुक, SQL सर्वर के लिए उच्च प्रदर्शन तकनीक, खंड 2 में प्रकाशित हुई थी। आप हमारी ई-बुक्स के बारे में यहां जान सकते हैं। यह भी ध्यान दें कि SQL सर्वर 2016 में इन-मेमोरी OLTP में नियोजित एन्हांसमेंट के साथ इनमें से कुछ चीजें बदल सकती हैं।
ट्रांजैक्ट-एसक्यूएल कोड के संबंध में कुछ आदतें और सर्वोत्तम प्रथाएं हैं जो समय के साथ विकसित होती हैं। विशेष रूप से संग्रहीत प्रक्रियाओं के साथ, हम सही डेटा प्रकार के पैरामीटर मानों को पारित करने का प्रयास करते हैं, और हमारे पैरामीटर को स्पष्ट रूप से नाम देते हैं न कि केवल क्रमिक स्थिति पर निर्भर करते हैं। कभी-कभी, हालांकि, हम इसके बारे में आलसी हो सकते हैं:हम एक यूनिकोड स्ट्रिंग को N
के साथ उपसर्ग करना भूल सकते हैं , या पैरामीटर नाम निर्दिष्ट करने के बजाय केवल स्थिरांक या चरों को क्रम में सूचीबद्ध करें। या दोनों।
SQL सर्वर 2014 में, यदि आप इन-मेमोरी OLTP ("हेकाटन") और मूल रूप से संकलित प्रक्रियाओं का उपयोग कर रहे हैं, तो आप इन चीजों पर अपनी सोच को थोड़ा समायोजित करना चाह सकते हैं। मैं कोडप्लेक्स पर SQL Server 2014 RTM इन-मेमोरी OLTP सैंपल के विरुद्ध कुछ कोड के साथ प्रदर्शित करूंगा, जो AdventureWorks2012 नमूना डेटाबेस का विस्तार करता है। (यदि आप इसे शुरू से आगे बढ़ाने के लिए सेट अप करने जा रहे हैं, तो कृपया पिछली पोस्ट में मेरी टिप्पणियों पर एक त्वरित नज़र डालें।)
आइए संग्रहित प्रक्रिया के लिए हस्ताक्षर पर एक नज़र डालें Sales.usp_InsertSpecialOffer_inmem
:
CREATE PROCEDURE [Sales].[usp_InsertSpecialOffer_inmem] @Description NVARCHAR(255) NOT NULL, @DiscountPct SMALLMONEY NOT NULL = 0, @Type NVARCHAR(50) NOT NULL, @Category NVARCHAR(50) NOT NULL, @StartDate DATETIME2 NOT NULL, @EndDate DATETIME2 NOT NULL, @MinQty INT NOT NULL = 0, @MaxQty INT = NULL, @SpecialOfferID INT OUTPUT WITH NATIVE_COMPILATION, SCHEMABINDING, EXECUTE AS OWNER AS BEGIN ATOMIC WITH (TRANSACTION ISOLATION LEVEL=SNAPSHOT, LANGUAGE=N'us_english') DECLARE @msg nvarchar(256) -- validation removed for brevity INSERT Sales.SpecialOffer_inmem (Description, DiscountPct, Type, Category, StartDate, EndDate, MinQty, MaxQty) VALUES (@Description, @DiscountPct, @Type, @Category, @StartDate, @EndDate, @MinQty, @MaxQty) SET @SpecialOfferID = SCOPE_IDENTITY() END GO
मैं उत्सुक था कि क्या यह मायने रखता है कि क्या मापदंडों का नाम दिया गया था, या यदि मूल रूप से संकलित प्रक्रियाओं ने निहित रूपांतरणों को पारंपरिक संग्रहीत प्रक्रियाओं की तुलना में किसी भी बेहतर संग्रहीत प्रक्रियाओं के तर्क के रूप में संभाला। सबसे पहले मैंने एक कॉपी बनाई Sales.usp_InsertSpecialOffer_inmem
एक पारंपरिक संग्रहित प्रक्रिया के रूप में - इसमें केवल ATOMIC
. को हटाना शामिल था NOT NULL
. को ब्लॉक करना और हटाना इनपुट पैरामीटर से घोषणाएं:
CREATE PROCEDURE [Sales].[usp_InsertSpecialOffer] @Description NVARCHAR(255), @DiscountPct SMALLMONEY = 0, @Type NVARCHAR(50), @Category NVARCHAR(50), @StartDate DATETIME2, @EndDate DATETIME2, @MinQty INT = 0, @MaxQty INT = NULL, @SpecialOfferID INT OUTPUT AS BEGIN DECLARE @msg nvarchar(256) -- validation removed for brevity INSERT Sales.SpecialOffer_inmem (Description, DiscountPct, Type, Category, StartDate, EndDate, MinQty, MaxQty) VALUES (@Description, @DiscountPct, @Type, @Category, @StartDate, @EndDate, @MinQty, @MaxQty) SET @SpecialOfferID = SCOPE_IDENTITY() END GO
स्थानांतरण मानदंड को कम करने के लिए, प्रक्रिया अभी भी तालिका के इन-मेमोरी संस्करण, Sales.SpecialOffer_inmem में सम्मिलित होती है।
तब मैं इन मानदंडों के साथ संग्रहीत कार्यविधि की दोनों प्रतियों के लिए 100,000 कॉल करना चाहता था:
स्पष्ट रूप से नामित पैरामीटर | पैरामीटर नामित नहीं हैं | |
---|---|---|
सही डेटा प्रकार के सभी पैरामीटर | x | x |
गलत डेटा प्रकार के कुछ पैरामीटर | x | x |
निम्न बैच का उपयोग करके, संग्रहीत कार्यविधि के पारंपरिक संस्करण के लिए कॉपी किया गया (बस _inmem
को हटा रहा है) चार EXEC
. से कॉल):
SET NOCOUNT ON; CREATE TABLE #x ( i INT IDENTITY(1,1), d VARCHAR(32), s DATETIME2(7) NOT NULL DEFAULT SYSDATETIME(), e DATETIME2(7) ); GO INSERT #x(d) VALUES('Named, proper types'); GO /* this uses named parameters, and uses correct data types */ DECLARE @p1 NVARCHAR(255) = N'Product 1', @p2 SMALLMONEY = 10, @p3 NVARCHAR(50) = N'Volume Discount', @p4 NVARCHAR(50) = N'Reseller', @p5 DATETIME2 = '20140615', @p6 DATETIME2 = '20140620', @p7 INT = 10, @p8 INT = 20, @p9 INT; EXEC Sales.usp_InsertSpecialOffer_inmem @Description = @p1, @DiscountPct = @p2, @Type = @p3, @Category = @p4, @StartDate = @p5, @EndDate = @p6, @MinQty = @p7, @MaxQty = @p8, @SpecialOfferID = @p9 OUTPUT; GO 100000 UPDATE #x SET e = SYSDATETIME() WHERE i = 1; GO DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1'; GO INSERT #x(d) VALUES('Not named, proper types'); GO /* this does not use named parameters, but uses correct data types */ DECLARE @p1 NVARCHAR(255) = N'Product 1', @p2 SMALLMONEY = 10, @p3 NVARCHAR(50) = N'Volume Discount', @p4 NVARCHAR(50) = N'Reseller', @p5 DATETIME2 = '20140615', @p6 DATETIME2 = '20140620', @p7 INT = 10, @p8 INT = 20, @p9 INT; EXEC Sales.usp_InsertSpecialOffer_inmem @p1, @p2, @p3, @p4, @p5, @p6, @p7, @p8, @p9 OUTPUT; GO 100000 UPDATE #x SET e = SYSDATETIME() WHERE i = 2; GO DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1'; GO INSERT #x(d) VALUES('Named, improper types'); GO /* this uses named parameters, but incorrect data types */ DECLARE @p1 VARCHAR(255) = 'Product 1', @p2 DECIMAL(10,2) = 10, @p3 VARCHAR(255) = 'Volume Discount', @p4 VARCHAR(32) = 'Reseller', @p5 DATETIME = '20140615', @p6 CHAR(8) = '20140620', @p7 TINYINT = 10, @p8 DECIMAL(10,2) = 20, @p9 BIGINT; EXEC Sales.usp_InsertSpecialOffer_inmem @Description = @p1, @DiscountPct = @p2, @Type = @p3, @Category = @p4, @StartDate = @p5, @EndDate = @p6, @MinQty = '10', @MaxQty = @p8, @SpecialOfferID = @p9 OUTPUT; GO 100000 UPDATE #x SET e = SYSDATETIME() WHERE i = 3; GO DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1'; GO INSERT #x(d) VALUES('Not named, improper types'); GO /* this does not use named parameters, and uses incorrect data types */ DECLARE @p1 VARCHAR(255) = 'Product 1', @p2 DECIMAL(10,2) = 10, @p3 VARCHAR(255) = 'Volume Discount', @p4 VARCHAR(32) = 'Reseller', @p5 DATETIME = '20140615', @p6 CHAR(8) = '20140620', @p7 TINYINT = 10, @p8 DECIMAL(10,2) = 20, @p9 BIGINT; EXEC Sales.usp_InsertSpecialOffer_inmem @p1, @p2, @p3, @p4, @p5, @p6, '10', @p8, @p9 OUTPUT; GO 100000 UPDATE #x SET e = SYSDATETIME() WHERE i = 4; GO DELETE Sales.SpecialOffer_inmem WHERE Description = N'Product 1'; GO SELECT d, duration_ms = DATEDIFF(MILLISECOND, s, e) FROM #x; GO DROP TABLE #x; GO
मैंने प्रत्येक परीक्षण को 10 बार चलाया, और यहां औसत अवधि मिलीसेकंड में दी गई:
पारंपरिक संग्रहित प्रक्रिया | |
---|---|
पैरामीटर | औसत अवधि (मिलीसेकंड) |
नामांकित, उचित प्रकार | 72,132 |
नाम नहीं, उचित प्रकार | 72,846 |
नामांकित, अनुचित प्रकार | 76,154 |
नाम नहीं, अनुचित प्रकार | 76,902 |
मूल रूप से संकलित संग्रहित प्रक्रिया | |
पैरामीटर | औसत अवधि (मिलीसेकंड) |
नामांकित, उचित प्रकार | 63,202 |
नाम नहीं, उचित प्रकार | 61,297 |
नामांकित, अनुचित प्रकार | 64,560 |
नाम नहीं, अनुचित प्रकार | 64,288 |
विभिन्न कॉल विधियों की औसत अवधि, मिलीसेकंड में
पारंपरिक संग्रहीत कार्यविधि के साथ, यह स्पष्ट है कि गलत डेटा प्रकारों का उपयोग करने से प्रदर्शन (लगभग 4 सेकंड का अंतर) पर काफी प्रभाव पड़ता है, जबकि मापदंडों का नाम न देने का नाटकीय प्रभाव बहुत कम था (लगभग 700ms जोड़ना)। मैंने हमेशा सर्वोत्तम प्रथाओं का पालन करने और सही डेटा प्रकारों का उपयोग करने के साथ-साथ सभी मापदंडों को नाम देने की कोशिश की है, और यह छोटा परीक्षण इस बात की पुष्टि करता है कि ऐसा करना फायदेमंद हो सकता है।
मूल रूप से संकलित संग्रहीत कार्यविधि के साथ, गलत डेटा प्रकारों का उपयोग करने के कारण प्रदर्शन में पारंपरिक संग्रहीत कार्यविधि के समान ही गिरावट आई। इस बार, हालांकि, मापदंडों के नामकरण से बहुत मदद नहीं मिली; वास्तव में, इसका नकारात्मक प्रभाव पड़ा, जिससे कुल अवधि में लगभग दो सेकंड जुड़ गए। निष्पक्ष होने के लिए, यह काफी कम समय में बड़ी संख्या में कॉल है, लेकिन यदि आप इस सुविधा से पूर्णत:सबसे अधिक ब्लीडिंग-एज प्रदर्शन को निचोड़ने का प्रयास कर रहे हैं, तो हर नैनोसेकंड मायने रखता है।
समस्या का पता लगाना
आप कैसे जान सकते हैं कि आपकी मूल रूप से संकलित संग्रहीत प्रक्रियाओं को इन "धीमी" विधियों में से किसी एक के साथ बुलाया जा रहा है या नहीं? उसके लिए एक XEvent है! इवेंट को natively_compiled_proc_slow_parameter_passing
कहा जाता है , और ऐसा लगता है कि इस समय पुस्तकें ऑनलाइन में प्रलेखित नहीं है। इस घटना की निगरानी के लिए आप निम्नलिखित विस्तारित ईवेंट सत्र बना सकते हैं:
CREATE EVENT SESSION [XTP_Parameter_Events] ON SERVER ADD EVENT sqlserver.natively_compiled_proc_slow_parameter_passing ( ACTION(sqlserver.sql_text) ) ADD TARGET package0.event_file(SET filename=N'C:\temp\XTPParams.xel'); GO ALTER EVENT SESSION [XTP_Parameter_Events] ON SERVER STATE = START;
एक बार सत्र चलने के बाद, आप उपरोक्त चार कॉलों में से कोई भी व्यक्तिगत रूप से आज़मा सकते हैं, और फिर आप इस क्वेरी को चला सकते हैं:
;WITH x([timestamp], db, [object_id], reason, batch) AS ( SELECT xe.d.value(N'(event/@timestamp)[1]',N'datetime2(0)'), DB_NAME(xe.d.value(N'(event/data[@name="database_id"]/value)[1]',N'int')), xe.d.value(N'(event/data[@name="object_id"]/value)[1]',N'int'), xe.d.value(N'(event/data[@name="reason"]/text)[1]',N'sysname'), xe.d.value(N'(event/action[@name="sql_text"]/value)[1]',N'nvarchar(max)') FROM sys.fn_xe_file_target_read_file(N'C:\temp\XTPParams*.xel',NULL,NULL,NULL) AS ft CROSS APPLY (SELECT CONVERT(XML, ft.event_data)) AS xe(d) ) SELECT [timestamp], db, [object_id], reason, batch FROM x;
आपने जो चलाया उसके आधार पर, आपको इसके समान परिणाम देखने चाहिए:
टाइमस्टैम्प | <थ>डीबीऑब्जेक्ट_आईडी | <थ>कारण <थ>बैच|||
---|---|---|---|---|
2014-07-01 16:23:14 | AdventureWorks2012 | 2087678485 | नाम_पैरामीटर | DECLARE @p1 NVARCHAR(255) = N'Product 1', @p2 SMALLMONEY = 10, @p3 NVARCHAR(50) = N'Volume Discount', @p4 NVARCHAR(50) = N'Reseller', @p5 DATETIME2 = '20140615', @p6 DATETIME2 = '20140620', @p7 INT = 10, @p8 INT = 20, @p9 INT; EXEC Sales.usp_InsertSpecialOffer_inmem @Description = @p1, @DiscountPct = @p2, @Type = @p3, @Category = @p4, @StartDate = @p5, @EndDate = @p6, @MinQty = @p7, @MaxQty = @p8, @SpecialOfferID = @p9 OUTPUT; |
2014-07-01 16:23:22 | AdventureWorks2012 | 2087678485 | पैरामीटर_रूपांतरण | DECLARE @p1 VARCHAR(255) = 'Product 1', @p2 DECIMAL(10,2) = 10, @p3 VARCHAR(255) = 'Volume Discount', @p4 VARCHAR(32) = 'Reseller', @p5 DATETIME = '20140615', @p6 CHAR(8) = '20140620', @p7 TINYINT = 10, @p8 DECIMAL(10,2) = 20, @p9 BIGINT; EXEC Sales.usp_InsertSpecialOffer_inmem @p1, @p2, @p3, @p4, @p5, @p6, '10', @p8, @p9 OUTPUT; |
विस्तारित घटनाओं से नमूना परिणाम
उम्मीद है कि बैच
कॉलम अपराधी की पहचान करने के लिए पर्याप्त है, लेकिन अगर आपके पास बड़े बैच हैं जिनमें मूल रूप से संकलित प्रक्रियाओं के लिए कई कॉल हैं और आपको उन वस्तुओं को ट्रैक करने की आवश्यकता है जो विशेष रूप से इस समस्या को ट्रिगर कर रहे हैं, तो आप बस उन्हें object_idद्वारा देख सकते हैं। कोड> उनके संबंधित डेटाबेस में।
अब, मैं सत्र के सक्रिय रहने के दौरान पाठ में सभी 400,000 कॉल चलाने या अत्यधिक समवर्ती, उत्पादन परिवेश में इस सत्र को चालू करने की अनुशंसा नहीं करता - यदि आप इसे बहुत अधिक कर रहे हैं, तो यह कुछ महत्वपूर्ण ओवरहेड का कारण बन सकता है। आप अपने विकास या मंचन के माहौल में इस तरह की गतिविधि की जाँच करने से बहुत बेहतर हैं, जब तक कि आप इसे एक पूर्ण व्यावसायिक चक्र को कवर करने वाले उचित कार्यभार के अधीन कर सकते हैं।
निष्कर्ष
मैं निश्चित रूप से इस तथ्य से हैरान था कि नामकरण पैरामीटर - लंबे समय से एक सर्वोत्तम अभ्यास माना जाता है - को मूल रूप से संकलित संग्रहीत प्रक्रियाओं के साथ सबसे खराब अभ्यास में बदल दिया गया है। और यह Microsoft द्वारा एक संभावित समस्या के लिए पर्याप्त रूप से जाना जाता है कि उन्होंने इसे ट्रैक करने के लिए विशेष रूप से डिज़ाइन किया गया एक विस्तारित ईवेंट बनाया है। यदि आप इन-मेमोरी ओएलटीपी का उपयोग कर रहे हैं, तो यह एक ऐसी चीज है जिसे आपको अपने रडार पर रखना चाहिए क्योंकि आप सहायक संग्रहीत प्रक्रियाओं को विकसित करते हैं। मुझे पता है कि मुझे निश्चित रूप से नामित मापदंडों का उपयोग करने से अपनी मांसपेशियों की मेमोरी को अन-ट्रेन करना होगा।