अंततः, क्वेरी में केवल एक बार एक स्केलर UDF का मूल्यांकन करने के लिए SQL सर्वर को मजबूर करना संभव नहीं है। हालाँकि, कुछ कदम हैं जिन्हें प्रोत्साहित करने के लिए यह कदम उठाया जा सकता है। परीक्षण के साथ मेरा मानना है कि आप SQL सर्वर के वर्तमान संस्करण के साथ काम करने वाले कुछ प्राप्त कर सकते हैं, लेकिन यह संभव है कि भविष्य के परिवर्तनों के लिए आपको अपने कोड को फिर से देखना होगा।
यदि कोड को संपादित करना संभव है तो कोशिश करने के लिए एक अच्छी बात यह है कि यदि संभव हो तो फ़ंक्शन नियतात्मक बनाना चाहिए। पॉल व्हाइट यहां बताते हैं कि फ़ंक्शन को SCHEMABINDING
विकल्प के साथ बनाया जाना चाहिए और फ़ंक्शन कोड स्वयं को नियतात्मक होना चाहिए।
निम्नलिखित परिवर्तन करने के बाद:
CREATE OR ALTER FUNCTION dbo.EXPENSIVE_UDF () RETURNS INT
WITH SCHEMABINDING
AS
BEGIN
DECLARE @tbl TABLE (VAL VARCHAR(5));
-- make the function expensive to call
INSERT INTO @tbl
SELECT [VALUE]
FROM STRING_SPLIT(REPLICATE(CAST('Z ' AS VARCHAR(MAX)), 20000), ' ');
RETURN 1;
END;
प्रश्न से प्रश्न 64 एमएस में निष्पादित किया जाता है:
SELECT x1.ID
FROM dbo.X_100_INTEGERS x1
WHERE x1.ID >= dbo.EXPENSIVE_UDF();
क्वेरी प्लान में अब फ़िल्टर ऑपरेटर नहीं है:
यह सुनिश्चित करने के लिए कि यह केवल एक बार निष्पादित होता है जब हम SQL Server 2016 में जारी नए sysinos_exec_function_stats DMV का उपयोग कर सकते हैं :
SELECT execution_count
FROM sys.dm_exec_function_stats
WHERE object_id = OBJECT_ID('EXPENSIVE_UDF', 'FN');
एक जारी करने वाले ALTER
समारोह के खिलाफ रीसेट कर देगा execution_count
उस वस्तु के लिए। उपरोक्त क्वेरी 1 का अर्थ है कि फ़ंक्शन केवल एक बार निष्पादित किया गया था।
ध्यान दें कि सिर्फ इसलिए कि फ़ंक्शन नियतात्मक है इसका मतलब यह नहीं है कि किसी भी प्रश्न के लिए केवल एक बार मूल्यांकन किया जाएगा। वास्तव में, कुछ प्रश्नों को जोड़ने से SCHEMABINDING
प्रदर्शन में गिरावट आ सकती है। निम्नलिखित प्रश्न पर विचार करें:
WITH cte (UDF_VALUE) AS
(
SELECT DISTINCT dbo.EXPENSIVE_UDF() UDF_VALUE
)
SELECT ID
FROM dbo.X_100_INTEGERS
INNER JOIN cte ON ID >= cte.UDF_VALUE;
DISTINCT
फ़िल्टर ऑपरेटर से छुटकारा पाने के लिए सुपरफ़्लस को जोड़ा गया था। योजना आशाजनक लग रही है:
उसके आधार पर, किसी को उम्मीद होगी कि यूडीएफ एक बार मूल्यांकन किया जाएगा और नेस्टेड लूप में बाहरी तालिका के रूप में उपयोग किया जाएगा। हालाँकि, क्वेरी को मेरी मशीन पर चलाने के लिए 6446 ms लगते हैं। sys.dm_exec_function_stats
समारोह के अनुसार 100 बार निष्पादित किया गया था। यह कैसे संभव है? " कम्प्यूट स्केलर्स, एक्सप्रेशंस एंड एक्ज़ीक्यूशन प्लान परफॉर्मेंस " में, पॉल व्हाइट बताते हैं कि कंप्यूट स्केलर ऑपरेटर को टाल दिया जा सकता है:
अधिक बार नहीं, एक गणना स्केलर केवल एक अभिव्यक्ति को परिभाषित करता है; वास्तविक गणना को तब तक के लिए स्थगित कर दिया जाता है जब तक कि निष्पादन योजना में कुछ बाद में परिणाम की आवश्यकता होती है।
इस क्वेरी के लिए ऐसा लगता है कि UDF कॉल को तब तक के लिए स्थगित कर दिया गया था, जब तक कि उसे 100 बार मूल्यांकन नहीं किया गया था।
दिलचस्प है, UTEF के साथ SCHEMABINDING
मूल प्रश्न में परिभाषित नहीं होने पर CTE उदाहरण मेरी मशीन पर 71 एमएस में निष्पादित होता है । फ़ंक्शन को केवल एक बार निष्पादित किया जाता है जब क्वेरी चलाया जाता है। यहाँ उस के लिए क्वेरी योजना है:
यह स्पष्ट नहीं है कि गणना स्केलर को स्थगित क्यों नहीं किया गया है। ऐसा इसलिए हो सकता है क्योंकि फ़ंक्शन का नॉनडेटर्मिज्म ऑपरेटरों के पुनर्व्यवस्थापन को सीमित करता है जो क्वेरी ऑप्टिमाइज़र कर सकता है।
एक वैकल्पिक तरीका यह है कि सीटीई में एक छोटी तालिका को जोड़ा जाए और उस तालिका में एकमात्र पंक्ति को क्वेरी करें। कोई भी छोटा टेबल करेगा, लेकिन निम्नलिखित का उपयोग करें:
CREATE TABLE dbo.X_ONE_ROW_TABLE (ID INT NOT NULL);
INSERT INTO dbo.X_ONE_ROW_TABLE VALUES (1);
क्वेरी तब बन जाती है:
WITH cte (UDF_VALUE) AS
(
SELECT DISTINCT dbo.EXPENSIVE_UDF() UDF_VALUE
FROM dbo.X_ONE_ROW_TABLE
)
SELECT ID
FROM dbo.X_100_INTEGERS
INNER JOIN cte ON ID >= cte.UDF_VALUE;
dbo.X_ONE_ROW_TABLE
अनुकूलक के लिए अनिश्चितता को जोड़ता है। यदि तालिका में शून्य पंक्तियाँ हैं तो CTE 0 पंक्तियों को लौटा देगा। किसी भी स्थिति में, ऑप्टिमाइज़र यह गारंटी नहीं दे सकता कि UDF निर्धारक नहीं होने पर CTE एक पंक्ति में वापस आ जाएगी, इसलिए ऐसा लगता है कि UDF का मूल्यांकन जॉइन से पहले किया जाएगा। मुझे उम्मीद है कि ऑप्टिमाइज़र स्कैन dbo.X_ONE_ROW_TABLE
करने के लिए, एक पंक्ति का अधिकतम मूल्य प्राप्त करने के लिए एक स्ट्रीम एग्रीगेट का उपयोग करेगा (जिसे फ़ंक्शन का मूल्यांकन करने की आवश्यकता है), और उपयोग करने के लिए कि नेस्टेड लूप के लिए बाहरी तालिका dbo.X_100_INTEGERS
मुख्य क्वेरी में शामिल हो । । ऐसा होता प्रतीत होता है :
मेरे मशीन पर लगभग 110 ms में क्वेरी निष्पादित होती है और UDF का मूल्यांकन केवल एक बार के अनुसार किया जाता है sys.dm_exec_function_stats
। यह कहना गलत नहीं होगा कि क्वेरी ऑप्टिमाइज़र को केवल एक बार यूडीएफ का मूल्यांकन करने के लिए मजबूर किया जाता है। हालांकि, एक आशावादी पुनर्लेखन की कल्पना करना कठिन है जो कि यूडीएफ के आसपास की सीमाओं और लागत स्केलर लागत की गणना के साथ कम लागत वाले प्रश्न को जन्म देगा।
सारांश में, नियतात्मक कार्यों के लिए (जिसमें SCHEMABINDING
विकल्प शामिल होना चाहिए ) क्वेरी को यथासंभव सरल तरीके से लिखने का प्रयास करें। यदि SQL Server 2016 या बाद के संस्करण पर, पुष्टि करें कि फ़ंक्शन का उपयोग करते समय केवल निष्पादित किया गया था sys.dm_exec_function_stats
। निष्पादन की योजना उस संबंध में भ्रामक हो सकती है।
SQL सर्वर द्वारा निर्धारक नहीं होने वाले कार्यों के लिए, SCHEMABINDING
विकल्प की कमी के साथ कुछ भी शामिल है , एक दृष्टिकोण UDF को सावधानी से तैयार किए गए CTE या व्युत्पन्न तालिका में रखना है। इसके लिए थोड़ी देखभाल की आवश्यकता होती है लेकिन एक ही सीटीई नियतात्मक और नॉनडेर्मिनिस्टिक दोनों कार्यों के लिए काम कर सकता है।