मैंने थोड़ा अलग दृष्टिकोण लिया, मुख्य रूप से यह देखने के लिए कि यह तकनीक दूसरों की तुलना कैसे करेगी, क्योंकि विकल्प अच्छा है, है ना?
परीक्षण
क्यों नहीं हम केवल यह देखते हुए शुरू करते हैं कि विभिन्न तरीकों को एक दूसरे के खिलाफ कैसे खड़ा किया गया है। मैंने परीक्षण के तीन सेट किए:
- पहला सेट बिना डीबी संशोधनों के साथ चला
- इंडेक्स-
TransactionDate
आधारित प्रश्नों के समर्थन के लिए एक इंडेक्स बनाए जाने के बाद दूसरा सेट चला Production.TransactionHistory
।
- तीसरे सेट ने थोड़ी अलग धारणा बनाई। चूंकि सभी तीन परीक्षण उत्पादों की एक ही सूची के खिलाफ चले थे, अगर हम उस सूची को कैश कर दें तो क्या होगा? मेरी विधि एक इन-मेमोरी कैश का उपयोग करती है जबकि अन्य विधियां एक समान अस्थायी तालिका का उपयोग करती हैं। परीक्षणों के इस सेट के लिए परीक्षण के दूसरे सेट के लिए बनाया गया समर्थन सूचकांक अभी भी मौजूद है।
अतिरिक्त परीक्षण विवरण:
- परीक्षण
AdventureWorks2012
SQL सर्वर 2012, SP2 (डेवलपर संस्करण) के विरुद्ध चलाए गए थे ।
- प्रत्येक परीक्षण के लिए मैंने जिनके उत्तर को लेबल किया था, उनसे मैंने प्रश्न लिया था और यह किस विशेष प्रश्न से था।
- मैंने क्वेरी विकल्प के "निष्पादन के बाद परिणाम त्यागें" विकल्प का उपयोग किया परिणाम।
- कृपया ध्यान दें कि परीक्षणों के पहले दो सेटों के
RowCounts
लिए, मेरी विधि के लिए "बंद" दिखाई दें। यह मेरा तरीका है कि जो CROSS APPLY
कुछ भी कर रहा है उसका एक मैनुअल कार्यान्वयन होने के कारण : यह प्रारंभिक क्वेरी को चलाता है Production.Product
और 161 पंक्तियों को वापस प्राप्त करता है, जो तब इसके खिलाफ प्रश्नों के लिए उपयोग करता है Production.TransactionHistory
। इसलिए, RowCount
मेरी प्रविष्टियों के लिए मूल्य हमेशा अन्य प्रविष्टियों की तुलना में 161 अधिक हैं। परीक्षणों के तीसरे सेट में (कैशिंग के साथ) पंक्ति गणना सभी विधियों के लिए समान है।
- मैंने निष्पादन योजनाओं पर भरोसा करने के बजाय आँकड़ों को पकड़ने के लिए SQL सर्वर प्रोफाइलर का उपयोग किया। हारून और मिकेल ने पहले से ही अपने प्रश्नों के लिए योजनाओं को दिखाते हुए एक अच्छा काम किया और उस जानकारी को पुन: पेश करने की आवश्यकता नहीं है। और मेरी पद्धति का उद्देश्य प्रश्नों को इतने सरल रूप में कम करना है कि यह वास्तव में मायने नहीं रखेगा। Profiler उपयोग करने का एक अतिरिक्त कारण है, लेकिन बाद में इसका उल्लेख किया जाएगा।
Name >= N'M' AND Name < N'S'
निर्माण का उपयोग करने के बजाय , मैंने उपयोग करना चुना Name LIKE N'[M-R]%'
, और SQL सर्वर उनके साथ एक जैसा व्यवहार करता है।
परिणाम
कोई सहायक सूचकांक नहीं
यह अनिवार्य रूप से आउट-ऑफ-द-बॉक्स AdventureWorks2012 है। सभी मामलों में मेरी विधि स्पष्ट रूप से कुछ अन्य की तुलना में बेहतर है, लेकिन शीर्ष 1 या 2 विधियों के रूप में कभी भी अच्छा नहीं है।
टेस्ट 1
आरोन का सीटीई स्पष्ट रूप से यहां विजेता है।
टेस्ट 2
आरोन का सीटीई (फिर से) और मिकेल का दूसरा apply row_number()
तरीका एक करीबी दूसरा है।
टेस्ट 3
हारून का सीटीई (फिर) विजेता है।
निष्कर्ष
जब कोई सहायक सूचकांक नहीं होता है TransactionDate
, तो मेरी विधि मानक करने से बेहतर है CROSS APPLY
, लेकिन फिर भी, सीटीई विधि का उपयोग स्पष्ट रूप से जाने का तरीका है।
सहायक सूचकांक (कोई कैशिंग नहीं) के साथ
परीक्षणों के इस सेट के लिए मैंने TransactionHistory.TransactionDate
उस क्षेत्र में सभी प्रकार के प्रश्नों के क्रम से स्पष्ट सूचकांक को जोड़ा । मैं कहता हूं कि "स्पष्ट" क्योंकि अधिकांश अन्य उत्तर भी इस बिंदु पर सहमत हैं। और चूंकि प्रश्न सभी सबसे हाल की तिथियां चाहते हैं , इसलिए TransactionDate
फ़ील्ड को आदेश दिया जाना चाहिए DESC
, इसलिए मैंने केवल CREATE INDEX
मिकेल के जवाब के निचले हिस्से में बयान पकड़ा और एक स्पष्ट जोड़ा FILLFACTOR
:
CREATE INDEX [IX_TransactionHistoryX]
ON Production.TransactionHistory (ProductID ASC, TransactionDate DESC)
WITH (FILLFACTOR = 100);
एक बार जब यह सूचकांक लागू हो जाता है, तो परिणाम काफी बदल जाते हैं।
टेस्ट 1
इस बार यह मेरी विधि है जो कम से कम लॉजिकल रीड्स के मामले में आगे आती है। CROSS APPLY
विधि, पहले टेस्ट 1 के लिए सबसे खराब अभिनेता, अवधि पर जीतता है और यहां तक कि CTE विधि धड़कता तार्किक पढ़ता है पर।
टेस्ट 2
इस बार यह मिकेल की पहली apply row_number()
विधि है जो रीड्स को देखते हुए विजेता है, जबकि पहले यह सबसे खराब प्रदर्शन करने वालों में से एक था। और अब मेरी विधि एक बहुत करीबी दूसरे स्थान पर आती है जब रीड्स को देखते हैं। वास्तव में, सीटीई पद्धति के बाहर, बाकी सभी रीड्स के संदर्भ में काफी करीब हैं।
टेस्ट 3
यहां सीटीई अभी भी विजेता है, लेकिन अब सूचकांक बनाने से पहले मौजूद कठोर अंतर की तुलना में अन्य तरीकों के बीच का अंतर मुश्किल से ध्यान देने योग्य है।
निष्कर्ष
मेरी विधि की प्रयोज्यता अब अधिक स्पष्ट है, हालांकि यह उचित अनुक्रमणिका नहीं होने के कारण कम लचीला है।
समर्थन सूचकांक और कैशिंग के साथ
परीक्षणों के इस सेट के लिए मैंने कैशिंग का उपयोग किया क्योंकि, ठीक है, क्यों नहीं? मेरा तरीका इन-मेमोरी कैशिंग का उपयोग करने की अनुमति देता है जो अन्य तरीकों तक नहीं पहुंच सकता है। इसलिए निष्पक्ष होने के लिए, मैंने निम्नलिखित अस्थायी तालिका बनाई जो Product.Product
सभी तीन परीक्षणों में उन अन्य तरीकों में सभी संदर्भों के लिए उपयोग की गई थी । इस DaysToManufacture
क्षेत्र का उपयोग केवल टेस्ट नंबर 2 में किया जाता है, लेकिन एक ही तालिका का उपयोग करने के लिए SQL स्क्रिप्ट के अनुरूप होना आसान था और इसे वहां होने में कोई चोट नहीं आई।
CREATE TABLE #Products
(
ProductID INT NOT NULL PRIMARY KEY,
Name NVARCHAR(50) NOT NULL,
DaysToManufacture INT NOT NULL
);
INSERT INTO #Products (ProductID, Name, DaysToManufacture)
SELECT p.ProductID, p.Name, p.DaysToManufacture
FROM Production.Product p
WHERE p.Name >= N'M' AND p.Name < N'S'
AND EXISTS (
SELECT *
FROM Production.TransactionHistory th
WHERE th.ProductID = p.ProductID
);
ALTER TABLE #Products REBUILD WITH (FILLFACTOR = 100);
टेस्ट 1
सभी तरीकों से कैशिंग से समान रूप से लाभ होता है, और मेरी विधि अभी भी आगे निकलती है।
टेस्ट 2
यहां अब हम लाइनअप में अंतर देखते हैं क्योंकि मेरी विधि मुश्किल से आगे निकलती है, केवल 2 रीड्स मिकेल की पहली apply row_number()
विधि से बेहतर है , जबकि कैशिंग के बिना मेरी विधि 4 रीड्स से पीछे थी।
परीक्षण 3
कृपया नीचे (लाइन के नीचे) की ओर अपडेट देखें । यहाँ हम फिर से कुछ अंतर देखते हैं। हारून की CROSS APPLY विधि की तुलना में मेरी विधि का "पैरामीटरयुक्त" स्वाद अब बमुश्किल 2 रीड की अगुवाई में है (बिना कैशिंग के वे समान थे)। लेकिन वास्तव में अजीब बात यह है कि पहली बार हम एक ऐसी विधि देखते हैं जो कैशिंग से नकारात्मक रूप से प्रभावित होती है: हारून की सीटीई विधि (जो पहले टेस्ट नंबर 3 के लिए सबसे अच्छी थी)। लेकिन, मैं इसका श्रेय नहीं लेने जा रहा हूं, जहां इसकी कोई वजह नहीं है, और चूंकि बिना कोचिंग के हारून की सीटीई पद्धति अभी भी मेरी पद्धति से तेज है, इसलिए कैचिंग के साथ, इस विशेष स्थिति के लिए सबसे अच्छा तरीका है आरोन का सीटीई तरीका।
निष्कर्ष कृपया नीचे की ओर अद्यतन देखें (पंक्ति के नीचे)
स्थिति जो एक द्वितीयक क्वेरी के परिणामों का बार-बार उपयोग करती है, अक्सर (लेकिन हमेशा नहीं) उन परिणामों को कैशिंग से लाभ मिलता है। लेकिन जब कैशिंग एक लाभ है, तो कहा गया है कि कैशिंग के लिए मेमोरी का उपयोग करने से अस्थायी तालिकाओं का उपयोग करने पर कुछ लाभ होता है।
प्रक्रिया
आम तौर पर
मैंने "हैडर" क्वेरी को अलग कर दिया है (यानी एस प्राप्त कर रहा है ProductID
, और एक मामले में भी DaysToManufacture
, Name
कुछ अक्षरों के साथ शुरुआत के आधार पर ) "विस्तार" क्वेरी (यानी TransactionID
एस और TransactionDate
एस हो रही है ) से। यह अवधारणा बहुत ही सरल प्रश्न करने के लिए थी और आशावादी को उनके साथ जुड़ने पर भ्रमित होने की अनुमति नहीं थी। स्पष्ट रूप से यह हमेशा फायदेमंद नहीं होता है क्योंकि यह ऑप्टिमाइज़र को अच्छी तरह से अनुकूलित कर देता है। लेकिन जैसा कि हमने परिणामों में देखा, क्वेरी के प्रकार के आधार पर, इस पद्धति की अपनी खूबियां हैं।
इस विधि के विभिन्न स्वादों के बीच अंतर हैं:
स्थिरांक: मापदंडों के बजाय इनलाइन स्थिरांक के रूप में किसी भी बदली मूल्यों को प्रस्तुत करें। यह ProductID
तीनों परीक्षणों में संदर्भित होगा और टेस्ट 2 में लौटने के लिए पंक्तियों की संख्या भी होगी क्योंकि यह " DaysToManufacture
उत्पाद विशेषता का पांच गुना" का एक फ़ंक्शन है । इस उप-विधि का अर्थ है कि प्रत्येक ProductID
को अपनी निष्पादन योजना प्राप्त होगी, जो कि अगर डेटा वितरण के लिए व्यापक भिन्नता है तो लाभकारी हो सकती है ProductID
। लेकिन अगर डेटा वितरण में थोड़ी भिन्नता है, तो अतिरिक्त योजनाओं के निर्माण की लागत संभवतः इसके लायक नहीं होगी।
पैरामिट्रीकृत: कम से कम जमा ProductID
के रूप में @ProductID
, कार्य योजना लागू करके कैशिंग और पुन: उपयोग के लिए अनुमति देता है। एक पैरामीटर के रूप में टेस्ट 2 के लिए लौटने के लिए पंक्तियों की चर संख्या का इलाज करने के लिए एक अतिरिक्त परीक्षण विकल्प भी है।
ऑप्टिमाइज़ अनजान: जब संदर्भ के ProductID
रूप में @ProductID
, यदि डेटा वितरण की व्यापक विविधता है, तो एक योजना को कैश करना संभव है जो अन्य ProductID
मूल्यों पर नकारात्मक प्रभाव डालता है इसलिए यह जानना अच्छा होगा कि इस क्वेरी संकेत का उपयोग करने से कोई मदद मिलती है।
कैश उत्पाद:Production.Product
हर बार तालिका को क्वेरी करने के बजाय , केवल उसी सूची को प्राप्त करने के लिए, क्वेरी को एक बार चलाएं (और जब हम उस पर हों, तो किसी भी ProductID
s को फ़िल्टर करें जो TransactionHistory
तालिका में भी नहीं हैं इसलिए हम किसी को बर्बाद नहीं करते हैं वहाँ संसाधन) और उस सूची को कैश करें। सूची में DaysToManufacture
फ़ील्ड शामिल होना चाहिए । इस विकल्प का उपयोग करते हुए पहले निष्पादन के लिए लॉजिकल रीड्स पर थोड़ा अधिक प्रारंभिक हिट होता है, लेकिन इसके बाद यह केवल TransactionHistory
तालिका होती है जिसे क्वैरी किया जाता है।
विशेष रूप से
ठीक है, लेकिन हां, उम, कैसे एक CURSOR का उपयोग किए बिना और एक अस्थायी तालिका या टेबल चर के लिए सेट प्रत्येक परिणाम डंपिंग के बिना सभी उप-प्रश्नों को जारी करना संभव है? स्पष्ट रूप से CURSOR / Temp Table मेथड को करने से रीड्स और राइट्स में स्पष्ट रूप से प्रतिबिंबित होगा। खैर, SQLCLR :) का उपयोग करके। SQLCLR संग्रहीत कार्यविधि बनाकर, मैं परिणाम सेट खोलने में सक्षम था और अनिवार्य रूप से प्रत्येक सब-क्वेरी के परिणामों को स्ट्रीम कर सकता था, एक निरंतर परिणाम सेट के रूप में (और एकाधिक परिणाम सेट नहीं)। उत्पाद जानकारी के बाहर (यानी ProductID
, Name
है, औरDaysToManufacture
), सब-क्वेरी परिणामों में से किसी को भी कहीं भी संग्रहीत नहीं किया जाना था (मेमोरी या डिस्क) और बस SQLCLR संग्रहीत कार्यविधि के मुख्य परिणाम सेट के रूप में पारित किया गया। इसने मुझे उत्पाद की जानकारी प्राप्त करने के लिए एक सरल क्वेरी करने और फिर इसके माध्यम से चक्र करने की अनुमति दी, जिसके खिलाफ बहुत ही सरल प्रश्न जारी किए TransactionHistory
।
और, यही कारण है कि मुझे आँकड़ों को पकड़ने के लिए SQL Server Profiler का उपयोग करना पड़ा। SQLCLR संग्रहीत कार्यविधि निष्पादन योजना नहीं लौटी, या तो "वास्तविक निष्पादन योजना शामिल करें" क्वेरी विकल्प, या जारी करके SET STATISTICS XML ON;
।
उत्पाद जानकारी कैशिंग के लिए, मैंने एक readonly static
जेनेरिक सूची (यानी _GlobalProducts
नीचे दिए गए कोड में) का उपयोग किया। ऐसा लगता है कि संग्रह में जोड़ना readonly
विकल्प का उल्लंघन नहीं करता है , इसलिए यह कोड तब काम करता है जब असेंबली PERMISSON_SET
का SAFE
:) होता है, भले ही वह काउंटर-सहज हो।
उत्पन्न क्वेरी
इस SQLCLR संग्रहीत कार्यविधि द्वारा निर्मित क्वेरी निम्नानुसार हैं:
उत्पाद की जानकारी
टेस्ट नंबर 1 और 3 (कोई कैशिंग नहीं)
SELECT prod1.ProductID, prod1.Name, 1 AS [DaysToManufacture]
FROM Production.Product prod1
WHERE prod1.Name LIKE N'[M-R]%';
टेस्ट नंबर 2 (कोई कैशिंग नहीं)
;WITH cte AS
(
SELECT prod1.ProductID
FROM Production.Product prod1 WITH (INDEX(AK_Product_Name))
WHERE prod1.Name LIKE N'[M-R]%'
)
SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
FROM Production.Product prod2
INNER JOIN cte
ON cte.ProductID = prod2.ProductID;
टेस्ट नंबर 1, 2 और 3 (कैशिंग)
;WITH cte AS
(
SELECT prod1.ProductID
FROM Production.Product prod1 WITH (INDEX(AK_Product_Name))
WHERE prod1.Name LIKE N'[M-R]%'
AND EXISTS (
SELECT *
FROM Production.TransactionHistory th
WHERE th.ProductID = prod1.ProductID
)
)
SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
FROM Production.Product prod2
INNER JOIN cte
ON cte.ProductID = prod2.ProductID;
लेन-देन की जानकारी
टेस्ट नंबर 1 और 2 (लगातार)
SELECT TOP (5) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = 977
ORDER BY th.TransactionDate DESC;
टेस्ट नंबर 1 और 2 (पैरामीटरकृत)
SELECT TOP (5) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
;
टेस्ट नंबर 1 और 2 (परिमापित + वैकल्पिक अंक)
SELECT TOP (5) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
OPTION (OPTIMIZE FOR (@ProductID UNKNOWN));
टेस्ट नंबर 2 (दोनों को परिमाणित)
SELECT TOP (@RowsToReturn) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
;
टेस्ट नंबर 2 (दोनों को अलग-अलग घोषित करें)
SELECT TOP (@RowsToReturn) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
OPTION (OPTIMIZE FOR (@ProductID UNKNOWN));
टेस्ट नंबर 3 (लगातार)
SELECT TOP (1) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = 977
ORDER BY th.TransactionDate DESC, th.TransactionID DESC;
परीक्षण संख्या 3 (परिमाणित)
SELECT TOP (1) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC, th.TransactionID DESC
;
परीक्षण संख्या 3 (परिमित + वैकल्पिक अंक)
SELECT TOP (1) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC, th.TransactionID DESC
OPTION (OPTIMIZE FOR (@ProductID UNKNOWN));
कोड
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using Microsoft.SqlServer.Server;
public class ObligatoryClassName
{
private class ProductInfo
{
public int ProductID;
public string Name;
public int DaysToManufacture;
public ProductInfo(int ProductID, string Name, int DaysToManufacture)
{
this.ProductID = ProductID;
this.Name = Name;
this.DaysToManufacture = DaysToManufacture;
return;
}
}
private static readonly List<ProductInfo> _GlobalProducts = new List<ProductInfo>();
private static void PopulateGlobalProducts(SqlBoolean PrintQuery)
{
if (_GlobalProducts.Count > 0)
{
if (PrintQuery.IsTrue)
{
SqlContext.Pipe.Send(String.Concat("I already haz ", _GlobalProducts.Count,
" entries :)"));
}
return;
}
SqlConnection _Connection = new SqlConnection("Context Connection = true;");
SqlCommand _Command = new SqlCommand();
_Command.CommandType = CommandType.Text;
_Command.Connection = _Connection;
_Command.CommandText = @"
;WITH cte AS
(
SELECT prod1.ProductID
FROM Production.Product prod1 WITH (INDEX(AK_Product_Name))
WHERE prod1.Name LIKE N'[M-R]%'
AND EXISTS (
SELECT *
FROM Production.TransactionHistory th
WHERE th.ProductID = prod1.ProductID
)
)
SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
FROM Production.Product prod2
INNER JOIN cte
ON cte.ProductID = prod2.ProductID;
";
SqlDataReader _Reader = null;
try
{
_Connection.Open();
_Reader = _Command.ExecuteReader();
while (_Reader.Read())
{
_GlobalProducts.Add(new ProductInfo(_Reader.GetInt32(0), _Reader.GetString(1),
_Reader.GetInt32(2)));
}
}
catch
{
throw;
}
finally
{
if (_Reader != null && !_Reader.IsClosed)
{
_Reader.Close();
}
if (_Connection != null && _Connection.State != ConnectionState.Closed)
{
_Connection.Close();
}
if (PrintQuery.IsTrue)
{
SqlContext.Pipe.Send(_Command.CommandText);
}
}
return;
}
[Microsoft.SqlServer.Server.SqlProcedure]
public static void GetTopRowsPerGroup(SqlByte TestNumber,
SqlByte ParameterizeProductID, SqlBoolean OptimizeForUnknown,
SqlBoolean UseSequentialAccess, SqlBoolean CacheProducts, SqlBoolean PrintQueries)
{
SqlConnection _Connection = new SqlConnection("Context Connection = true;");
SqlCommand _Command = new SqlCommand();
_Command.CommandType = CommandType.Text;
_Command.Connection = _Connection;
List<ProductInfo> _Products = null;
SqlDataReader _Reader = null;
int _RowsToGet = 5; // default value is for Test Number 1
string _OrderByTransactionID = "";
string _OptimizeForUnknown = "";
CommandBehavior _CmdBehavior = CommandBehavior.Default;
if (OptimizeForUnknown.IsTrue)
{
_OptimizeForUnknown = "OPTION (OPTIMIZE FOR (@ProductID UNKNOWN))";
}
if (UseSequentialAccess.IsTrue)
{
_CmdBehavior = CommandBehavior.SequentialAccess;
}
if (CacheProducts.IsTrue)
{
PopulateGlobalProducts(PrintQueries);
}
else
{
_Products = new List<ProductInfo>();
}
if (TestNumber.Value == 2)
{
_Command.CommandText = @"
;WITH cte AS
(
SELECT prod1.ProductID
FROM Production.Product prod1 WITH (INDEX(AK_Product_Name))
WHERE prod1.Name LIKE N'[M-R]%'
)
SELECT prod2.ProductID, prod2.Name, prod2.DaysToManufacture
FROM Production.Product prod2
INNER JOIN cte
ON cte.ProductID = prod2.ProductID;
";
}
else
{
_Command.CommandText = @"
SELECT prod1.ProductID, prod1.Name, 1 AS [DaysToManufacture]
FROM Production.Product prod1
WHERE prod1.Name LIKE N'[M-R]%';
";
if (TestNumber.Value == 3)
{
_RowsToGet = 1;
_OrderByTransactionID = ", th.TransactionID DESC";
}
}
try
{
_Connection.Open();
// Populate Product list for this run if not using the Product Cache
if (!CacheProducts.IsTrue)
{
_Reader = _Command.ExecuteReader(_CmdBehavior);
while (_Reader.Read())
{
_Products.Add(new ProductInfo(_Reader.GetInt32(0), _Reader.GetString(1),
_Reader.GetInt32(2)));
}
_Reader.Close();
if (PrintQueries.IsTrue)
{
SqlContext.Pipe.Send(_Command.CommandText);
}
}
else
{
_Products = _GlobalProducts;
}
SqlDataRecord _ResultRow = new SqlDataRecord(
new SqlMetaData[]{
new SqlMetaData("ProductID", SqlDbType.Int),
new SqlMetaData("Name", SqlDbType.NVarChar, 50),
new SqlMetaData("TransactionID", SqlDbType.Int),
new SqlMetaData("TransactionDate", SqlDbType.DateTime)
});
SqlParameter _ProductID = new SqlParameter("@ProductID", SqlDbType.Int);
_Command.Parameters.Add(_ProductID);
SqlParameter _RowsToReturn = new SqlParameter("@RowsToReturn", SqlDbType.Int);
_Command.Parameters.Add(_RowsToReturn);
SqlContext.Pipe.SendResultsStart(_ResultRow);
for (int _Row = 0; _Row < _Products.Count; _Row++)
{
// Tests 1 and 3 use previously set static values for _RowsToGet
if (TestNumber.Value == 2)
{
if (_Products[_Row].DaysToManufacture == 0)
{
continue; // no use in issuing SELECT TOP (0) query
}
_RowsToGet = (5 * _Products[_Row].DaysToManufacture);
}
_ResultRow.SetInt32(0, _Products[_Row].ProductID);
_ResultRow.SetString(1, _Products[_Row].Name);
switch (ParameterizeProductID.Value)
{
case 0x01:
_Command.CommandText = String.Format(@"
SELECT TOP ({0}) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC{2}
{1};
", _RowsToGet, _OptimizeForUnknown, _OrderByTransactionID);
_ProductID.Value = _Products[_Row].ProductID;
break;
case 0x02:
_Command.CommandText = String.Format(@"
SELECT TOP (@RowsToReturn) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = @ProductID
ORDER BY th.TransactionDate DESC
{0};
", _OptimizeForUnknown);
_ProductID.Value = _Products[_Row].ProductID;
_RowsToReturn.Value = _RowsToGet;
break;
default:
_Command.CommandText = String.Format(@"
SELECT TOP ({0}) th.TransactionID, th.TransactionDate
FROM Production.TransactionHistory th
WHERE th.ProductID = {1}
ORDER BY th.TransactionDate DESC{2};
", _RowsToGet, _Products[_Row].ProductID, _OrderByTransactionID);
break;
}
_Reader = _Command.ExecuteReader(_CmdBehavior);
while (_Reader.Read())
{
_ResultRow.SetInt32(2, _Reader.GetInt32(0));
_ResultRow.SetDateTime(3, _Reader.GetDateTime(1));
SqlContext.Pipe.SendResultsRow(_ResultRow);
}
_Reader.Close();
}
}
catch
{
throw;
}
finally
{
if (SqlContext.Pipe.IsSendingResults)
{
SqlContext.Pipe.SendResultsEnd();
}
if (_Reader != null && !_Reader.IsClosed)
{
_Reader.Close();
}
if (_Connection != null && _Connection.State != ConnectionState.Closed)
{
_Connection.Close();
}
if (PrintQueries.IsTrue)
{
SqlContext.Pipe.Send(_Command.CommandText);
}
}
}
}
टेस्ट क्वेरी
यहां परीक्षणों को पोस्ट करने के लिए पर्याप्त जगह नहीं है इसलिए मुझे एक और स्थान मिलेगा।
निष्कर्ष
कुछ परिदृश्यों के लिए, SQLCLR का उपयोग टी-एसक्यूएल में नहीं किए जा सकने वाले प्रश्नों के कुछ पहलुओं में हेरफेर करने के लिए किया जा सकता है। और अस्थायी तालिकाओं के बजाय कैशिंग के लिए मेमोरी का उपयोग करने की क्षमता है, हालांकि इसे संयमपूर्वक और सावधानी से किया जाना चाहिए क्योंकि स्मृति स्वचालित रूप से सिस्टम में वापस नहीं मिलती है। यह विधि भी ऐसी कोई चीज़ नहीं है जो तदर्थ प्रश्नों को मदद करेगी, हालाँकि इसे अधिक लचीला बनाना संभव है, जैसा कि मैंने यहाँ दिखाया है कि पैरामीटर को दर्ज़ करके क्वेरीज़ के अधिक पहलुओं को निष्पादित किया जा रहा है।
अपडेट करें
अतिरिक्त परीक्षण
मेरा मूल परीक्षण जिसमें TransactionHistory
निम्नलिखित परिभाषा का उपयोग करने पर एक सहायक सूचकांक शामिल था :
ProductID ASC, TransactionDate DESC
मैंने उस समय TransactionId DESC
अंत में निर्णय लेने का निर्णय लिया था, जिसमें यह अनुमान लगाया गया था कि यह टेस्ट नंबर 3 की मदद कर सकता है (जो सबसे हाल ही में टाई-ब्रेकिंग को निर्दिष्ट TransactionId
करता है, "सबसे हाल का" माना जाता है क्योंकि स्पष्ट रूप से नहीं बताया गया है, लेकिन हर कोई लगता है इस धारणा पर सहमत होने के लिए), एक अंतर बनाने के लिए पर्याप्त संबंध नहीं होने की संभावना है।
लेकिन, तब हारून ने एक सहायक सूचकांक के साथ संन्यास लिया जिसमें यह शामिल था TransactionId DESC
और पाया गया कि CROSS APPLY
विधि तीनों परीक्षणों में विजेता थी। यह मेरे परीक्षण से अलग था जिसने संकेत दिया कि टेस्ट नंबर 3 के लिए सीटीई पद्धति सबसे अच्छी थी (जब कोई कैशिंग का उपयोग नहीं किया गया था, जो हारून के परीक्षण को प्रतिबिंबित करता है)। यह स्पष्ट था कि एक अतिरिक्त भिन्नता थी जिसे जांचने की आवश्यकता थी।
मैंने करंट सपोर्टिंग इंडेक्स को हटा दिया, साथ में एक नया बनाया TransactionId
, और प्लान कैश को मंजूरी दे दी (बस सुनिश्चित किया जाए):
DROP INDEX [IX_TransactionHistoryX] ON Production.TransactionHistory;
CREATE UNIQUE INDEX [UIX_TransactionHistoryX]
ON Production.TransactionHistory (ProductID ASC, TransactionDate DESC, TransactionID DESC)
WITH (FILLFACTOR = 100);
DBCC FREEPROCCACHE WITH NO_INFOMSGS;
मैंने टेस्ट नंबर 1 को फिर से चलाया और परिणाम उम्मीद के मुताबिक ही थे। मैंने तब टेस्ट नंबर 3 को फिर से चलाया और परिणाम वास्तव में बदल गए:
उपरोक्त परिणाम मानक, गैर-कैशिंग परीक्षण के लिए हैं। इस बार, न केवल CROSS APPLY
सीटीई (आरोन के परीक्षण के संकेत के रूप में) को हराया, बल्कि एसक्यूसीएलआर की खरीद ने 30 रीड्स (वू हू) से बढ़त हासिल की।
उपरोक्त परिणाम कैशिंग सक्षम के साथ परीक्षण के लिए हैं। इस बार सीटीई के प्रदर्शन में गिरावट नहीं है, हालांकि CROSS APPLY
अभी भी यह धड़कता है। हालाँकि, अब SQLCLR खरीद 23 रीड्स (वू हू, फिर से) द्वारा लीड लेती है।
दूर ले जाओ
उपयोग करने के लिए विभिन्न विकल्प हैं। यह कोशिश करना सबसे अच्छा है क्योंकि उनमें से प्रत्येक के पास अपनी ताकत है। यहां किए गए परीक्षण सभी परीक्षणों के दौरान सर्वश्रेष्ठ और सबसे खराब प्रदर्शन करने वालों (समर्थन सूचकांक के साथ) के बीच रीड्स और अवधि दोनों में एक छोटा सा बदलाव दिखाते हैं; रीड्स में भिन्नता लगभग 350 है और अवधि 55 एमएस है। जबकि SQLCLR प्रॉपर्टी ने सभी लेकिन 1 टेस्ट (रीड्स के संदर्भ में) में जीत हासिल की, केवल कुछ रीड्स को सहेजना आमतौर पर SQLCLR रूट के रखरखाव लागत के लायक नहीं है। लेकिन AdventureWorks2012 में, Product
तालिका में केवल 504 पंक्तियाँ हैं और TransactionHistory
केवल 113,443 पंक्तियाँ हैं। इन विधियों में प्रदर्शन अंतर संभवतः अधिक स्पष्ट हो जाता है क्योंकि पंक्ति की संख्या बढ़ जाती है।
जबकि यह प्रश्न पंक्तियों के एक विशेष सेट को प्राप्त करने के लिए विशिष्ट था, पर यह ध्यान नहीं दिया जाना चाहिए कि प्रदर्शन का सबसे बड़ा कारक अनुक्रमण था और विशेष SQL नहीं। वास्तव में सबसे अच्छा तरीका निर्धारित करने से पहले एक अच्छे सूचकांक की आवश्यकता होती है।
यहां पाया जाने वाला सबसे महत्वपूर्ण सबक CROSS APPLY बनाम CTE बनाम SQLCLR के बारे में नहीं है: यह परीक्षण के बारे में है। मत मानो। कई लोगों से विचार प्राप्त करें और जितने परिदृश्य आप कर सकते हैं उतने परीक्षण करें।