प्राथमिक और विदेशी कुंजी संबंधपरक डेटाबेस की मूलभूत विशेषताएं हैं, जैसा कि मूल रूप से ई.एफ. कॉड के पेपर, "बड़े साझा डेटा बैंकों के लिए डेटा का एक रिलेशनल मॉडल", 1970 में प्रकाशित हुआ था। अक्सर दोहराया जाने वाला उद्धरण है, "कुंजी, पूरी कुंजी, और कुछ नहीं बस कुंजी, इसलिए कॉड मेरी मदद करें।"
पृष्ठभूमि :प्राथमिक कुंजी
प्राथमिक कुंजी SQL सर्वर में एक बाधा है, जो तालिका में प्रत्येक पंक्ति को विशिष्ट रूप से पहचानने का कार्य करती है। कुंजी को एकल गैर-नल कॉलम, या गैर-नल कॉलम के संयोजन के रूप में परिभाषित किया जा सकता है जो एक अद्वितीय मान उत्पन्न करता है, और तालिका के लिए इकाई अखंडता को लागू करने के लिए उपयोग किया जाता है। एक तालिका में केवल एक प्राथमिक कुंजी हो सकती है, और जब किसी तालिका के लिए प्राथमिक कुंजी बाधा परिभाषित की जाती है, तो एक अद्वितीय अनुक्रमणिका बनाई जाती है। वह अनुक्रमणिका डिफ़ॉल्ट रूप से एक संकुल अनुक्रमणिका होगी, जब तक कि प्राथमिक कुंजी बाधा परिभाषित होने पर गैर-संकुल अनुक्रमणिका के रूप में निर्दिष्ट न हो।
Sales.SalesOrderHeader
. पर विचार करें AdventureWorks2012
में तालिका डेटाबेस। इस तालिका में विक्रय आदेश के बारे में मूलभूत जानकारी होती है, जिसमें आदेश दिनांक और ग्राहक आईडी शामिल है, और प्रत्येक बिक्री की विशिष्ट रूप से एक SalesOrderID
द्वारा पहचान की जाती है , जो तालिका के लिए प्राथमिक कुंजी है। हर बार तालिका में एक नई पंक्ति जोड़ी जाती है, प्राथमिक कुंजी बाधा (नाम PK_SalesOrderHeader_SalesOrderID
) को यह सुनिश्चित करने के लिए चेक किया जाता है कि SalesOrderID
. के लिए समान मान वाली कोई पंक्ति पहले से मौजूद नहीं है ।
विदेशी कुंजी
प्राथमिक कुंजी से अलग, लेकिन बहुत अधिक संबंधित, विदेशी कुंजी हैं। एक विदेशी कुंजी एक स्तंभ या स्तंभों का संयोजन है जो प्राथमिक कुंजी के समान है, लेकिन एक अलग तालिका में है। दो तालिकाओं के बीच संबंध को परिभाषित करने और अखंडता को लागू करने के लिए विदेशी कुंजियों का उपयोग किया जाता है।
उपरोक्त उदाहरण का उपयोग जारी रखने के लिए, SalesOrderID
Sales.SalesOrderDetail
. में एक विदेशी कुंजी के रूप में कॉलम मौजूद है तालिका, जहां बिक्री के बारे में अतिरिक्त जानकारी संग्रहीत की जाती है, जैसे उत्पाद आईडी और मूल्य। जब SalesOrderHeader
में एक नई बिक्री जोड़ी जाती है तालिका, उस बिक्री के लिए SalesOrderDetail
. में एक पंक्ति जोड़ने की आवश्यकता नहीं है तालिका हालांकि, SalesOrderDetail
. में एक पंक्ति जोड़ते समय तालिका, SalesOrderID
. के लिए एक संगत पंक्ति चाहिए SalesOrderHeader
. में मौजूद है टेबल।
इसके विपरीत, डेटा हटाते समय, एक विशिष्ट SalesOrderID
. के लिए एक पंक्ति SalesOrderDetail
. से किसी भी समय हटाया जा सकता है तालिका, लेकिन SalesOrderHeader
. से एक पंक्ति को हटाने के लिए तालिका, संबंधित पंक्तियाँ SalesOrderDetail
. से पहले हटाना होगा।
प्राथमिक कुंजी बाधाओं के विपरीत, जब किसी तालिका के लिए एक विदेशी कुंजी बाधा परिभाषित की जाती है, तो SQL सर्वर द्वारा डिफ़ॉल्ट रूप से एक अनुक्रमणिका नहीं बनाई जाती है। हालाँकि, डेवलपर्स और डेटाबेस व्यवस्थापकों के लिए उन्हें मैन्युअल रूप से जोड़ना असामान्य नहीं है। विदेशी कुंजी तालिका के लिए एक समग्र प्राथमिक कुंजी का हिस्सा हो सकती है, इस स्थिति में क्लस्टरिंग कुंजी के हिस्से के रूप में विदेशी कुंजी के साथ एक क्लस्टर इंडेक्स मौजूद होगा। वैकल्पिक रूप से, क्वेरी के लिए एक इंडेक्स की आवश्यकता हो सकती है जिसमें तालिका में विदेशी कुंजी और एक या अधिक अतिरिक्त कॉलम शामिल हों, इसलिए उन प्रश्नों का समर्थन करने के लिए एक गैर-संकुल सूचकांक बनाया जाएगा। इसके अलावा, विदेशी कुंजी पर अनुक्रमणिका प्राथमिक और विदेशी कुंजी को शामिल करने वाले टेबल जॉइन के लिए प्रदर्शन लाभ प्रदान कर सकते हैं, और जब प्राथमिक कुंजी मान अपडेट किया जाता है, या यदि पंक्ति हटा दी जाती है तो वे प्रदर्शन को प्रभावित कर सकते हैं।
AdventureWorks2012
. में डेटाबेस, एक टेबल है, SalesOrderDetail
, SalesOrderID
. के साथ एक विदेशी कुंजी के रूप में। SalesOrderDetail
. के लिए तालिका, SalesOrderID
और SalesOrderDetailID
क्लस्टर इंडेक्स द्वारा समर्थित प्राथमिक कुंजी बनाने के लिए गठबंधन करें। अगर SalesOrderDetail
तालिका में SalesOrderID
. पर कोई अनुक्रमणिका नहीं थी कॉलम, फिर जब SalesOrderHeader
. से एक पंक्ति हटा दी जाती है , SQL सर्वर को यह सत्यापित करना होगा कि समान SalesOrderID
. के लिए कोई पंक्तियाँ नहीं हैं मूल्य मौजूद है। बिना किसी अनुक्रमणिका के जिसमें SalesOrderID
. हो कॉलम, SQL सर्वर को SalesOrderDetail
. का एक पूर्ण तालिका स्कैन करने की आवश्यकता होगी . जैसा कि आप कल्पना कर सकते हैं, संदर्भित तालिका जितनी बड़ी होगी, हटाने में उतना ही अधिक समय लगेगा।
एक उदाहरण
हम इसे निम्नलिखित उदाहरण में देख सकते हैं, जो AdventureWorks2012
से उपरोक्त तालिकाओं की प्रतियों का उपयोग करता है। डेटाबेस जिसे एक स्क्रिप्ट का उपयोग करके विस्तारित किया गया है जिसे यहां पाया जा सकता है। स्क्रिप्ट जोनाथन केहैयस (ब्लॉग | @SQLPoolBoy) द्वारा विकसित की गई थी और एक SalesOrderHeaderEnlarged
बनाता है 1,258,600 पंक्तियों वाली तालिका, और एक SalesOrderDetailEnlarged
4,852,680 पंक्तियों वाली तालिका। स्क्रिप्ट चलाने के बाद, नीचे दिए गए कथनों का उपयोग करके विदेशी कुंजी बाधा को जोड़ा गया था। ध्यान दें कि बाधा ON DELETE CASCADE
. के साथ बनाई गई है विकल्प। इस विकल्प के साथ, जब SalesOrderHeaderEnlarged
के विरुद्ध कोई अपडेट या डिलीट जारी किया जाता है तालिका, संबंधित तालिका में पंक्तियाँ - इस मामले में बस SalesOrderDetailEnlarged
- अपडेट या डिलीट कर दिए जाते हैं।
इसके अलावा, SalesOrderDetailEnglarged
. के लिए डिफ़ॉल्ट, संकुल अनुक्रमणिका हटा दिया गया था और केवल SalesOrderDetailID
. के लिए फिर से बनाया गया था प्राथमिक कुंजी के रूप में, क्योंकि यह एक विशिष्ट डिज़ाइन का प्रतिनिधित्व करती है।
USE [AdventureWorks2012]; GO /* remove original clustered index */ ALTER TABLE [Sales].[SalesOrderDetailEnlarged] DROP CONSTRAINT [PK_SalesOrderDetailEnlarged_SalesOrderID_SalesOrderDetailID]; GO /* re-create clustered index with SalesOrderDetailID only */ ALTER TABLE [Sales].[SalesOrderDetailEnlarged] ADD CONSTRAINT [PK_SalesOrderDetailEnlarged_SalesOrderDetailID] PRIMARY KEY CLUSTERED ( [SalesOrderDetailID] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]; GO /* add foreign key constraint for SalesOrderID */ ALTER TABLE [Sales].[SalesOrderDetailEnlarged] WITH CHECK ADD CONSTRAINT [FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID] FOREIGN KEY([SalesOrderID]) REFERENCES [Sales].[SalesOrderHeaderEnlarged] ([SalesOrderID]) ON DELETE CASCADE; GO ALTER TABLE [Sales].[SalesOrderDetailEnlarged] CHECK CONSTRAINT [FK_SalesOrderDetailEnlarged_SalesOrderHeaderEnlarged_SalesOrderID]; GO
विदेशी कुंजी बाधा और कोई सहायक अनुक्रमणिका नहीं होने के कारण, SalesOrderHeaderEnlarged
के विरुद्ध एक एकल डिलीट जारी किया गया था तालिका, जिसके परिणामस्वरूप SalesOrderHeaderEnlarged
. से एक पंक्ति हटा दी गई और SalesOrderDetailEnlarged
. से 72 पंक्तियाँ :
SET STATISTICS IO ON; SET STATISTICS TIME ON; DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; USE [AdventureWorks2012]; GO DELETE FROM [Sales].[SalesOrderHeaderEnlarged] WHERE [SalesOrderID] = 292104;
आँकड़ों IO और समय की जानकारी ने निम्नलिखित दिखाया:
SQL सर्वर पार्स और संकलन समय:CPU समय =8 ms, बीता हुआ समय =8 ms.
तालिका 'SalesOrderDetailEnlarged'। स्कैन काउंट 1, लॉजिकल रीड्स 50647, फिजिकल रीड्स 8, रीड-फॉरवर्ड रीड्स 50667, लोब लॉजिकल रीड्स 0, लोब फिजिकल रीड्स 0, लोब रीड-आगे रीड्स 0.
टेबल 'वर्कटेबल'। स्कैन काउंट 2, लॉजिकल रीड्स 7, फिजिकल रीड्स 0, रीड-आगे रीड्स 0, लोब लॉजिकल रीड्स 0, लोब फिजिकल रीड्स 0, लोब रीड-अहेड रीड्स 0।
टेबल 'सेल्सऑर्डरहेडरएनलार्जेड'। स्कैन काउंट 0, लॉजिकल रीड्स 15, फिजिकल रीड्स 14, रीड-फॉरवर्ड रीड्स 0, लोब लॉजिकल रीड्स 0, लोब फिजिकल रीड्स 0, लोब रीड-फॉरवर्ड रीड 0.
SQL सर्वर निष्पादन समय:
CPU समय =1045 ms, बीता हुआ समय =1898 ms.
SQL संतरी योजना एक्सप्लोरर का उपयोग करते हुए, निष्पादन योजना SalesOrderDetailEnlarged
के विरुद्ध एक संकुल अनुक्रमणिका स्कैन दिखाती है क्योंकि SalesOrderID
. पर कोई अनुक्रमणिका नहीं है :
विदेशी कुंजी पर कोई अनुक्रमणिका नहीं के साथ क्वेरी योजना
SalesOrderDetailEnlarged
. का समर्थन करने के लिए गैर-संकुल अनुक्रमणिका तब निम्नलिखित कथन का उपयोग करके बनाया गया था:
USE [AdventureWorks2012]; GO /* create nonclustered index */ CREATE NONCLUSTERED INDEX [IX_SalesOrderDetailEnlarged_SalesOrderID] ON [Sales].[SalesOrderDetailEnlarged] ( [SalesOrderID] ASC ) WITH ( PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON ) ON [PRIMARY]; GO
एक और हटाना एक SalesOrderID
. के लिए क्रियान्वित किया गया था जिसने SalesOrderHeaderEnlarged
. में एक पंक्ति को प्रभावित किया और SalesOrderDetailEnlarged
. में 72 पंक्तियाँ :
SET STATISTICS IO ON; SET STATISTICS TIME ON; DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; USE [AdventureWorks2012]; GO DELETE FROM [Sales].[SalesOrderHeaderEnlarged] WHERE [SalesOrderID] = 697505;
आँकड़ों IO और समय की जानकारी ने नाटकीय सुधार दिखाया:
SQL सर्वर पार्स और संकलन समय:CPU समय =0 ms, बीता हुआ समय =7 ms.
तालिका 'SalesOrderDetailEnlarged'। स्कैन काउंट 1, लॉजिकल रीड्स 48, फिजिकल रीड्स 13, रीड-फॉरवर्ड रीड्स 0, लोब लॉजिकल रीड्स 0, लोब फिजिकल रीड्स 0, लोब रीड-आगे रीड्स 0.
टेबल 'वर्कटेबल'। स्कैन काउंट 2, लॉजिकल रीड्स 7, फिजिकल रीड्स 0, रीड-आगे रीड्स 0, लोब लॉजिकल रीड्स 0, लोब फिजिकल रीड्स 0, लोब रीड-अहेड रीड्स 0।
टेबल 'सेल्सऑर्डरहेडरएनलार्जेड'। स्कैन काउंट 0, लॉजिकल रीड्स 15, फिजिकल रीड्स 15, रीड-फॉरवर्ड रीड्स 0, लोब लॉजिकल रीड्स 0, लोब फिजिकल रीड्स 0, लोब रीड-आगे रीड्स 0।
SQL सर्वर निष्पादन समय:
CPU समय =0 ms, बीता हुआ समय =27 ms.
और क्वेरी योजना ने SalesOrderID
पर गैर-संकुलित अनुक्रमणिका का अनुक्रमणिका खोज दिखाया , जैसा कि अपेक्षित था:
विदेशी कुंजी पर इंडेक्स के साथ क्वेरी प्लान
क्वेरी निष्पादन समय 1898 एमएस से 27 एमएस तक गिर गया - एक 98.58% की कमी, और SalesOrderDetailEnlarged
के लिए पढ़ता है तालिका 50647 से घटकर 48 हो गई - 99.9% सुधार। प्रतिशत एक तरफ, केवल I/O को हटाने से उत्पन्न होने पर विचार करें। SalesOrderDetailEnlarged
इस उदाहरण में तालिका केवल 500 एमबी है, और 256 जीबी उपलब्ध मेमोरी वाले सिस्टम के लिए, बफर कैश में 500 एमबी लेने वाली तालिका एक भयानक स्थिति की तरह प्रतीत नहीं होती है। लेकिन 50 लाख पंक्तियों की एक तालिका अपेक्षाकृत छोटी है; अधिकांश बड़े OLTP सिस्टम में करोड़ों पंक्तियों वाली तालिकाएँ होती हैं। इसके अलावा, प्राथमिक कुंजी के लिए कई विदेशी कुंजी संदर्भ मौजूद होना असामान्य नहीं है, जहां प्राथमिक कुंजी को हटाने के लिए कई संबंधित तालिकाओं से हटाने की आवश्यकता होती है। उस स्थिति में, हटाए जाने के लिए विस्तारित अवधि देखना संभव है जो न केवल एक प्रदर्शन समस्या है, बल्कि एक अवरुद्ध समस्या भी है, जो अलगाव स्तर पर निर्भर करती है।
निष्कर्ष
आम तौर पर एक इंडेक्स बनाने की सिफारिश की जाती है जो विदेशी कुंजी कॉलम पर ले जाता है, न केवल प्राथमिक और विदेशी कुंजी के बीच जुड़ने का समर्थन करता है, बल्कि अपडेट और डिलीट भी करता है। ध्यान दें कि यह एक सामान्य अनुशंसा है, क्योंकि किनारे के मामले परिदृश्य हैं जहां विदेशी कुंजी पर अतिरिक्त अनुक्रमणिका का उपयोग अत्यंत छोटे तालिका आकार के कारण नहीं किया गया था, और अतिरिक्त अनुक्रमणिका अद्यतन वास्तव में नकारात्मक रूप से प्रदर्शन को प्रभावित करते थे। किसी भी स्कीमा संशोधनों के साथ, कार्यान्वयन के बाद सूचकांक परिवर्धन का परीक्षण और निगरानी की जानी चाहिए। यह सुनिश्चित करना महत्वपूर्ण है कि अतिरिक्त सूचकांक वांछित प्रभाव उत्पन्न करते हैं और समाधान प्रदर्शन को नकारात्मक रूप से प्रभावित नहीं करते हैं। यह भी ध्यान देने योग्य है कि विदेशी कुंजी के लिए अनुक्रमणिका द्वारा कितनी अतिरिक्त जगह की आवश्यकता है। इंडेक्स बनाने से पहले इस पर विचार करना आवश्यक है, और यदि वे लाभ प्रदान करते हैं, तो आगे बढ़ने की क्षमता योजना के लिए विचार किया जाना चाहिए।