एक विस्तार तालिका में एक मास्टर तालिका में शामिल होने पर, मैं SQL सर्वर 2014 को बड़ी (विस्तार) तालिका के कार्डिनैलिटी अनुमान में शामिल होने के लिए कार्डिनैलिटी अनुमान का उपयोग करने के लिए कैसे प्रोत्साहित कर सकता हूं?
उदाहरण के लिए, 10K मास्टर पंक्तियों को 100K विस्तार पंक्तियों में शामिल करते समय, मैं SQL सर्वर को 100K पंक्तियों में शामिल होने का अनुमान लगाना चाहता हूं - विस्तार पंक्तियों की अनुमानित संख्या के समान। SQL सर्वर के अनुमानक को इस तथ्य का लाभ उठाने में मदद करने के लिए मुझे अपने प्रश्नों और / या तालिकाओं और / या अनुक्रमिकाओं की संरचना कैसे करनी चाहिए कि प्रत्येक विवरण पंक्ति में हमेशा एक समान मास्टर पंक्ति हो? (मतलब कि उनके बीच एक जुड़ाव कभी भी कार्डिनैलिटी के अनुमान को कम नहीं करता है।)
यहाँ अधिक विवरण है। हमारे डेटाबेस में तालिकाओं की एक मास्टर / डिटेल जोड़ी है: VisitTarget
प्रत्येक बिक्री लेनदेन के लिए एक पंक्ति है, और VisitSale
प्रत्येक लेनदेन में प्रत्येक उत्पाद के लिए एक पंक्ति है। यह एक से कई संबंध है: 10 VisitSale पंक्तियों के औसत के लिए एक VisitTarget पंक्ति।
टेबल इस तरह दिखते हैं: (मैं इस प्रश्न के लिए केवल प्रासंगिक कॉलम को सरल बना रहा हूं)
-- "master" table
CREATE TABLE VisitTarget
(
VisitTargetId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
SaleDate date NOT NULL,
StoreId int NOT NULL
-- other columns omitted for clarity
);
-- covering index for date-scoped queries
CREATE NONCLUSTERED INDEX IX_VisitTarget_SaleDate
ON VisitTarget (SaleDate) INCLUDE (StoreId /*, ...more columns */);
-- "detail" table
CREATE TABLE VisitSale
(
VisitSaleId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
VisitTargetId int NOT NULL,
SaleDate date NOT NULL, -- denormalized; copied from VisitTarget
StoreId int NOT NULL, -- denormalized; copied from VisitTarget
ItemId int NOT NULL,
SaleQty int NOT NULL,
SalePrice decimal(9,2) NOT NULL
-- other columns omitted for clarity
);
-- covering index for date-scoped queries
CREATE NONCLUSTERED INDEX IX_VisitSale_SaleDate
ON VisitSale (SaleDate)
INCLUDE (VisitTargetId, StoreId, ItemId, SaleQty, TotalSalePrice decimal(9,2) /*, ...more columns */
);
ALTER TABLE VisitSale
WITH CHECK ADD CONSTRAINT FK_VisitSale_VisitTargetId
FOREIGN KEY (VisitTargetId)
REFERENCES VisitTarget (VisitTargetId);
ALTER TABLE VisitSale
CHECK CONSTRAINT FK_VisitSale_VisitTargetId;
प्रदर्शन कारणों से, हमने आंशिक रूप SaleDate
से प्रत्येक तालिका तालिका पंक्तियों में मास्टर तालिका से सबसे आम फ़िल्टरिंग कॉलम (जैसे ) की प्रतिलिपि बनाकर असामान्य किया है , और फिर हमने दिनांक-फ़िल्टर्ड क्वेरीज़ को बेहतर समर्थन करने के लिए दोनों तालिकाओं पर अनुक्रमणिका को शामिल किया। दिनांक फ़िल्टर किए गए प्रश्नों को चलाने पर I / O को कम करने के लिए यह बहुत अच्छा काम करता है, लेकिन मुझे लगता है कि यह दृष्टिकोण मास्टर और डिटेल टेबल को एक साथ जोड़ते समय कार्डिनैलिटी आकलन की समस्या पैदा कर रहा है।
जब हम इन दो तालिकाओं से जुड़ते हैं, तो प्रश्न इस तरह दिखते हैं:
SELECT vt.StoreId, vt.SomeOtherColumn, Sales = sum(vs.SalePrice*vs.SaleQty)
FROM VisitTarget vt
JOIN VisitSale vs on vt.VisitTargetId = vs.VisitTargetId
WHERE
vs.SaleDate BETWEEN '20170101' and '20171231'
and vt.SaleDate BETWEEN '20170101' and '20171231'
-- more filtering goes here, e.g. by store, by product, etc.
डिटेल टेबल ( VisitSale
) पर तिथि फ़िल्टर बेमानी है। डेट रेंज द्वारा फ़िल्टर किए गए प्रश्नों के लिए विस्तार तालिका में अनुक्रमिक I / O (उर्फ इंडेक्स सीक ऑपरेटर) को सक्षम करना है।
इस प्रकार के प्रश्नों की योजना इस प्रकार है:
एक ही समस्या के साथ एक क्वेरी की वास्तविक योजना यहां पाई जा सकती है ।
जैसा कि आप देख सकते हैं, जुड़ने के लिए कार्डिनिटी का अनुमान (चित्र में निचले-बाएं में टूलटिप) 4x से अधिक कम है: 2.1M वास्तविक बनाम 0.5M अनुमानित। यह प्रदर्शन समस्याओं का कारण बनता है (उदाहरण के लिए tempdb पर spilling), खासकर जब यह क्वेरी एक उपश्रेणी होती है जिसका उपयोग अधिक जटिल क्वेरी में किया जाता है।
लेकिन शामिल होने की प्रत्येक शाखा के लिए पंक्ति-गणना का अनुमान वास्तविक पंक्ति गणना के करीब है। सम्मिलित होने का शीर्ष आधा 100K वास्तविक बनाम 164K अनुमानित है। शामिल होने के निचले आधे हिस्से में 2.1M पंक्तियां वास्तविक बनाम 3.7M अनुमानित हैं। हैश बाल्टी वितरण भी अच्छा लगता है। ये अवलोकन मुझे सुझाव देते हैं कि आंकड़े प्रत्येक तालिका के लिए ठीक हैं, और समस्या यह है कि कार्डिनलिटी में शामिल होने का अनुमान है।
सबसे पहले मैंने सोचा था कि यह समस्या थी कि SQL सर्वर यह उम्मीद कर रहा है कि प्रत्येक तालिका में SaleDate कॉलम स्वतंत्र हैं, जबकि वास्तव में वे समान हैं। इसलिए मैंने बिक्री की तारीखों में शामिल होने की शर्त या WHERE क्लॉज, जैसे के लिए एक समानता तुलना जोड़ने की कोशिश की
ON vt.VisitTargetId = vs.VisitTargetId and vt.SaleDate = vs.SaleDate
या
WHERE vt.SaleDate = vs.SaleDate
यह काम नहीं किया। इसने कार्डिनैलिटी के अनुमानों को भी बदतर बना दिया! तो या तो SQL सर्वर उस समानता संकेत का उपयोग नहीं कर रहा है या कुछ और समस्या का मूल कारण है।
समस्या निवारण कैसे करें और उम्मीद करें कि इस कार्डिनैलिटी आकलन मुद्दे को कैसे ठीक किया जाए? मेरा लक्ष्य मास्टर / विस्तार में शामिल होने की कार्डिनैलिटी के लिए है जो कि जुड़ने के बड़े ("विस्तार तालिका") इनपुट के अनुमान के समान है।
अगर यह मायने रखता है, तो हम Windows Server पर SQL Server 2014 Enterprise SP2 CU8 का निर्माण 12.0.5557.0 से कर रहे हैं। कोई ट्रेस झंडे सक्षम नहीं हैं। डेटाबेस संगतता स्तर SQL सर्वर 2014 है। हम कई अलग-अलग SQL सर्वरों पर समान व्यवहार देखते हैं, इसलिए यह सर्वर-विशिष्ट समस्या होने की संभावना नहीं है।
SQL सर्वर 2014 कार्डिनैलिटी एस्टीमेटर में एक ऑप्टिमाइज़ेशन है जो ठीक वैसा ही व्यवहार है जैसा मैं देख रहा हूँ:
नए CE, हालांकि, एक सरल एल्गोरिथ्म का उपयोग करता है जो मानता है कि एक बड़ी तालिका और एक छोटी तालिका के बीच एक-से-कई सम्मिलित जुड़ाव है। यह मानता है कि बड़ी तालिका में प्रत्येक पंक्ति छोटी तालिका में ठीक एक पंक्ति से मेल खाती है। यह एल्गोरिथ्म बड़े इनपुट के अनुमानित आकार को कार्डिनैलिटी के रूप में लौटाता है।
आदर्श रूप से मुझे यह व्यवहार मिल सकता है, जहां शामिल होने के लिए कार्डिनिटी का अनुमान बड़ी तालिका के लिए अनुमान के समान होगा, भले ही मेरी "छोटी" तालिका अभी भी 100K पंक्तियों से अधिक वापस आ जाएगी!