अगस्त में वापस मैंने टी-एसक्यूएल मंगलवार के लिए मेरी स्कीमा-स्वैप पद्धति पर एक पोस्ट लिखा था। दृष्टिकोण अनिवार्य रूप से आपको उपयोगकर्ताओं के साथ हस्तक्षेप को कम करने के लिए पृष्ठभूमि में एक तालिका की एक प्रति (जैसे, किसी प्रकार की लुकअप तालिका) को आलसी लोड करने की अनुमति देता है:एक बार पृष्ठभूमि तालिका अद्यतित होने के बाद, अद्यतन डेटा वितरित करने के लिए आवश्यक सभी चीजें उपयोगकर्ताओं के लिए एक मेटाडेटा परिवर्तन करने के लिए काफी देर तक एक रुकावट है।
उस पोस्ट में, मैंने दो चेतावनियों का उल्लेख किया है कि जिस पद्धति का मैंने वर्षों से समर्थन किया है वह वर्तमान में पूरा नहीं करता है:विदेशी प्रमुख बाधाएं और आंकड़े . ऐसी कई अन्य विशेषताएं हैं जो इस तकनीक में भी हस्तक्षेप कर सकती हैं। एक जो हाल ही में बातचीत में सामने आया:ट्रिगर . और भी कुछ हैं:पहचान कॉलम , प्राथमिक कुंजी बाधाएं , डिफ़ॉल्ट बाधाएं , बाधाओं की जांच करें , यूडीएफ को संदर्भित करने वाली बाधाएं , सूचकांक , दृश्य (अनुक्रमित दृश्यों सहित , जिसके लिए SCHEMABINDING
. की आवश्यकता है ), और विभाजन . मैं आज इन सब से निपटने वाला नहीं हूं, लेकिन मैंने सोचा कि वास्तव में क्या होता है यह देखने के लिए मैं कुछ का परीक्षण करूंगा।
मैं स्वीकार करूंगा कि मेरा मूल समाधान मूल रूप से एक गरीब आदमी का स्नैपशॉट था, बिना सभी बाधाओं, संपूर्ण-डेटाबेस, और प्रतिकृति, मिररिंग और उपलब्धता समूहों जैसे समाधानों की लाइसेंसिंग आवश्यकताओं के बिना। ये उत्पादन से तालिकाओं की केवल-पढ़ने के लिए प्रतियां थीं जिन्हें टी-एसक्यूएल और स्कीमा स्वैप तकनीक का उपयोग करके "प्रतिबिंबित" किया जा रहा था। इसलिए उन्हें इनमें से किसी भी फैंसी कुंजी, बाधाओं, ट्रिगर्स और अन्य सुविधाओं की आवश्यकता नहीं थी। लेकिन मैं देखता हूं कि तकनीक अधिक परिदृश्यों में उपयोगी हो सकती है, और उन परिदृश्यों में उपरोक्त कुछ कारक काम में आ सकते हैं।
तो आइए तालिकाओं की एक साधारण जोड़ी सेट करें जिसमें इनमें से कई गुण हों, एक स्कीमा स्वैप करें, और देखें कि क्या टूटता है। :-)
सबसे पहले, स्कीमा:
CREATE SCHEMA prep;GOCREATE SCHEMA live;GOCREATE SCHEMA Holder;GO
अब, live
में तालिका एक ट्रिगर और एक UDF सहित स्कीमा:
CREATE FUNCTION dbo.udf()रिटर्न्स INT ASBEGIN RETURN (SELECT 20);ENDGO CREATE TABLE live.t1( id INT IDENTITY(1,1), int_column INT NOT NULL DEFAULT 1, udf_column INT NOT NULL DEFAULT dbo.udf (), कनवर्ट के रूप में कंप्यूटेड_कॉलम (INT, int_column + 1), CONSTRAINT pk_live प्राथमिक कुंजी (id), CONSTRAINT ck_live CHECK (int_column> 0)); GO क्रिएट TRIGGER live.trig_live.t1FOR INSERTASBEGIN 'प्रिंट';
अब, हम prep
. में टेबल की कॉपी के लिए भी यही बात दोहराते हैं . हमें ट्रिगर की दूसरी कॉपी भी चाहिए, क्योंकि हम prep
में ट्रिगर नहीं बना सकते स्कीमा जो live
में एक तालिका का संदर्भ देता है , या ठीक इसके विपरीत। हम जानबूझकर पहचान को एक उच्च बीज और int_column
के लिए एक भिन्न डिफ़ॉल्ट मान पर सेट करेंगे (कई स्कीमा स्वैप के बाद हम वास्तव में किस तालिका के साथ काम कर रहे हैं, इस पर बेहतर नज़र रखने में हमारी मदद करने के लिए):
टेबल प्रेप.t1 बनाएं (आईडी INT पहचान (1000,1), int_column INT NOT NULL DEFAULT 2, udf_column INT NOT NULL DBO.udf(), कंप्यूटेड_कॉलम एएस कन्वर्ट (INT, int_column + 1), CONSTRAINT pk_ KEY(id), CONSTRAINT ck_prep CHECK (int_column> 1));GO TRIGGER क्रिएट करें prep.trig_prepON prep.t1FOR INSERTASBEGIN PRINT 'prep.trig';ENDGO
अब, प्रत्येक तालिका में कुछ पंक्तियाँ डालें और आउटपुट देखें:
नोकाउंट चालू करें; INSERT live.t1 डिफ़ॉल्ट मान; INSERT live.t1 डिफ़ॉल्ट मान; INSERT prep.t1 डिफ़ॉल्ट मान; INSERT prep.t1 डिफ़ॉल्ट मान; चुनें * live.t1 से; चयन करें * प्रीप.t1 से;
परिणाम:
आईडी | int_column | udf_column | गणना_स्तंभ |
---|---|---|---|
1 | 1 | 20 | 2 |
2 | 1 | 20 | 2 |
live.t1 के परिणाम
आईडी | int_column | udf_column | गणना_स्तंभ |
---|---|---|---|
1000 | 2 | 20 | 3 |
1001 | 2 | 20 | 3 |
prep.t1 के परिणाम
और संदेश फलक में:
live.triglive.trig
prep.trig
prep.trig
अब, एक साधारण स्कीमा स्वैप करते हैं:
-- मान लें कि आप prep.t1 का बैकग्राउंड लोडिंग यहाँ करते हैं BEGIN TRANSACTION; ALTER SCHEMA धारक स्थानांतरण तैयारी t1; ALTER SCHEMA प्रस्तुत करने का स्थानान्तरण live.t1; ALTER SCHEMA लाइव ट्रांसफ़र होल्डर.t1;COMMIT TRANSACTION;
और फिर व्यायाम दोहराएं:
नोकाउंट चालू करें; INSERT live.t1 डिफ़ॉल्ट मान; INSERT live.t1 डिफ़ॉल्ट मान; INSERT prep.t1 डिफ़ॉल्ट मान; INSERT prep.t1 डिफ़ॉल्ट मान; चुनें * live.t1 से; चयन करें * प्रीप.t1 से;
तालिका में परिणाम ठीक लगते हैं:
आईडी | int_column | udf_column | गणना_स्तंभ |
---|---|---|---|
1 | 1 | 20 | 2 |
2 | 1 | 20 | 2 |
3 | 1 | 20 | 2 |
4 | 1 | 20 | 2 |
live.t1 के परिणाम
आईडी | int_column | udf_column | गणना_स्तंभ |
---|---|---|---|
1000 | 2 | 20 | 3 |
1001 | 2 | 20 | 3 |
1002 | 2 | 20 | 3 |
1003 | 2 | 20 | 3 |
prep.t1 के परिणाम
लेकिन संदेश फलक ट्रिगर आउटपुट को गलत क्रम में सूचीबद्ध करता है:
prep.trigprep.trig
live.trig
live.trig
तो, आइए सभी मेटाडेटा में खुदाई करें। यहां एक क्वेरी है जो सभी पहचान कॉलम, ट्रिगर्स, प्राथमिक कुंजी, डिफ़ॉल्ट और इन तालिकाओं के लिए बाधाओं की जांच करेगी, संबंधित ऑब्जेक्ट की स्कीमा, नाम और परिभाषा (और बीज/अंतिम मान के लिए) पर ध्यान केंद्रित करेगी। पहचान कॉलम):
चुनें [प्रकार] ='चेक', [स्कीमा] =OBJECT_SCHEMA_NAME (parent_object_id), नाम, [परिभाषा] FROM sys.check_constraintsWHERE OBJECT_SCHEMA_NAME(parent_object_id) IN (N'live',N'prep')UNION ALLSELECT [प्रकार ] ='डिफ़ॉल्ट', [स्कीमा] =OBJECT_SCHEMA_NAME (parent_object_id), नाम, [परिभाषा] FROM sys.default_constraintsWHERE OBJECT_SCHEMA_NAME(parent_object_id) IN (N'live',N'prep')UNION ALLSELECT [type] ='Trigger', [स्कीमा] =OBJECT_SCHEMA_NAME(parent_id), नाम, [परिभाषा] =OBJECT_DEFINITION([object_id]) sys.triggers से जहां OBJECT_SCHEMA_NAME(parent_id) IN (N'live',N'prep')UNION ALLSELECT [प्रकार'] ='पहचान' , [स्कीमा] =OBJECT_SCHEMA_NAME([object_id]), नाम ='बीज =' + कन्वर्ट (VARCHAR(12), Seed_value), [परिभाषा] ='last_value =' + CONVERT(VARCHAR(12), last_value)FROM sys. Identity_columnsWHERE OBJECT_SCHEMA_NAME([object_id]) IN (N'live',N'prep')UNION ALLSELECT [type] ='Primary key', [schema] =OBJECT_SCHEMA_NAME([parent_object_id]), नाम, [defin ition] =''FROM sys.key_constraintsWHERE OBJECT_SCHEMA_NAME([object_id]) IN (N'live',N'prep');
परिणाम काफी मेटाडेटा गड़बड़ी दर्शाते हैं:
टाइप करें | स्कीमा | <थ>नामपरिभाषा | |
---|---|---|---|
जांचें | तैयारी | ck_live | ([int_column]>(0)) |
जांचें | लाइव | ck_prep | ([int_column]>(1)) |
डिफ़ॉल्ट | तैयारी | df_live1 | ((1)) |
डिफ़ॉल्ट | तैयारी | df_live2 | ([dbo].[udf]()) |
डिफ़ॉल्ट | लाइव | df_prep1 | ((2)) |
डिफ़ॉल्ट | लाइव | df_prep2 | ([dbo].[udf]()) |
ट्रिगर | तैयारी | trig_live | CREATE TRIGGER live.trig_live ON live.t1 FOR INSERT AS BEGIN PRINT 'live.trig'; END |
ट्रिगर | लाइव | trig_prep | CREATE TRIGGER prep.trig_prep ON prep.t1 FOR INSERT AS BEGIN PRINT 'prep.trig'; END |
पहचान | तैयारी | बीज =1 | last_value =4 |
पहचान | लाइव | बीज =1000 | last_value =1003 |
प्राथमिक कुंजी | तैयारी | pk_live | |
प्राथमिक कुंजी | लाइव | pk_prep |
मेटाडेटा बतख-बतख-हंस
पहचान कॉलम और बाधाओं के साथ समस्याएं कोई बड़ी समस्या नहीं लगती हैं। भले ही ऑब्जेक्ट *लगता है* कैटलॉग दृश्यों के अनुसार गलत ऑब्जेक्ट की ओर इशारा करते हैं, कार्यक्षमता - कम से कम बुनियादी इंसर्ट के लिए - काम करती है जैसा कि आप उम्मीद कर सकते हैं यदि आपने मेटाडेटा को कभी नहीं देखा था।
बड़ी समस्या ट्रिगर के साथ है - एक पल के लिए भूल जाना कि मैंने इस उदाहरण को कितना तुच्छ बना दिया है, वास्तविक दुनिया में, यह शायद स्कीमा और नाम से आधार तालिका का संदर्भ देता है। किस मामले में, जब यह गलत तालिका से जुड़ा होता है, तो चीजें जा सकती हैं … ठीक है, गलत। आइए वापस स्विच करें:
लेनदेन शुरू करें; ALTER SCHEMA धारक स्थानांतरण तैयारी t1; ALTER SCHEMA प्रस्तुत करने का स्थानान्तरण live.t1; ALTER SCHEMA लाइव ट्रांसफ़र होल्डर.t1;COMMIT TRANSACTION;
(आप खुद को यह समझाने के लिए मेटाडेटा क्वेरी फिर से चला सकते हैं कि सब कुछ सामान्य हो गया है।)
आइए अब live
. पर ट्रिगर *only* बदलें संस्करण वास्तव में कुछ उपयोगी करने के लिए (ठीक है, इस प्रयोग के संदर्भ में "उपयोगी"):
ALTER TRIGGER live.trig_liveON live.t1फॉर INSERTASBEGIN SELECT i.id, msg ='live.trig' फ्रॉम इंसर्ट फ्रॉम इन इनर जॉइन लाइव.t1 AS t ON i.id =t.id;ENDGO
अब एक पंक्ति सम्मिलित करते हैं:
लाइव.t1 डिफ़ॉल्ट मान डालें;
परिणाम:
id msg-------- ----------5 live.trig
फिर स्वैप फिर से करें:
लेनदेन शुरू करें; ALTER SCHEMA धारक स्थानांतरण तैयारी t1; ALTER SCHEMA प्रस्तुत करने का स्थानान्तरण live.t1; ALTER SCHEMA लाइव ट्रांसफ़र होल्डर.t1;COMMIT TRANSACTION;
और दूसरी पंक्ति डालें:
लाइव.t1 डिफ़ॉल्ट मान डालें;
परिणाम (संदेश फलक में):
prep.trig
उह ओह। यदि हम इस स्कीमा को एक घंटे में एक बार स्वैप करते हैं, तो हर दिन में से 12 घंटे के लिए, ट्रिगर वह नहीं कर रहा है जो हम उससे करने की उम्मीद करते हैं, क्योंकि यह तालिका की गलत कॉपी से जुड़ा है! आइए अब ट्रिगर के "प्रीप" संस्करण को बदलें:
वैकल्पिक ट्रिगर तैयारी।परिणाम:
संदेश 208, स्तर 16, राज्य 6, प्रक्रिया ट्रिग_प्रेप, पंक्ति 1
अवैध वस्तु नाम 'prep.trig_prep'।खैर, यह निश्चित रूप से अच्छा नहीं है। चूंकि हम मेटाडेटा-इस-स्वैप चरण में हैं, ऐसी कोई वस्तु नहीं है; ट्रिगर अब
live.trig_prep
हैं औरprep.trig_live
. अभी तक भ्रमित? मैं भी। तो चलिए इसे आजमाते हैं:EXEC sp_helptext 'live.trig_prep';परिणाम:
ट्रिगर तैयार करें।अच्छा, यह मज़ेदार नहीं है? मैं इस ट्रिगर को कैसे बदल सकता हूँ जब इसका मेटाडेटा अपनी परिभाषा में भी ठीक से प्रतिबिंबित नहीं होता है? आइए इसे आजमाएं:
ALTER TRIGGER live.trig_prepON prep.t1INSERTASBEGIN SELECT i.id के लिए, msg ='prep.trig' फ्रॉम इंसर्टेड AS i INER JOIN prep.t1 AS t ON i.id =t.id;ENDGOपरिणाम:
संदेश 2103, स्तर 15, राज्य 1, प्रक्रिया ट्रिग_प्रेप, पंक्ति 1
ट्रिगर 'live.trig_prep' को परिवर्तित नहीं कर सकता क्योंकि इसका स्कीमा लक्ष्य तालिका या दृश्य के स्कीमा से भिन्न है।यह भी अच्छा नहीं है, जाहिर है। ऐसा लगता है कि इस परिदृश्य को हल करने का वास्तव में कोई अच्छा तरीका नहीं है जिसमें वस्तुओं को उनके मूल स्कीमा में वापस स्वैप करना शामिल नहीं है। मैं इस ट्रिगर को
live.t1
. के विरुद्ध होने के लिए बदल सकता हूं :ALTER TRIGGER live.trig_prepON live.t1इन्सर्टसबेगिन सिलेक्ट i.id के लिए, msg ='live.trig' फ्रॉम इनर जॉइन लाइव.t1 AS t ON i.id =t.id;ENDGOलेकिन अब मेरे पास दो ट्रिगर हैं जो कहते हैं, उनके बॉडी टेक्स्ट में, कि वे
live.t1
के विरुद्ध काम करते हैं , लेकिन केवल यह वास्तव में निष्पादित करता है। हां, मेरा सिर घूम रहा है (और इस ब्लॉग पोस्ट में माइकल जे। स्वार्ट (@MJSwart) भी था)। और ध्यान दें कि, इस गड़बड़ी को साफ करने के लिए, स्कीमा को फिर से स्वैप करने के बाद, मैं ट्रिगर्स को उनके मूल नामों से छोड़ सकता हूं:DROP TRIGGER live.trig_live;DROP TRIGGER prep.trig_prep;अगर मैं
DROP TRIGGER live.trig_prep;
try आज़माता हूँ , उदाहरण के लिए, मुझे एक वस्तु मिलती है त्रुटि नहीं मिली।संकल्प?
ट्रिगर समस्या का समाधान गतिशील रूप से
CREATE TRIGGER
. उत्पन्न करना है कोड, और स्वैप के हिस्से के रूप में ट्रिगर को छोड़ दें और फिर से बनाएं। सबसे पहले, एक ट्रिगर को वापस *current* टेबल परlive
. में डालते हैं (आप अपने परिदृश्य में तय कर सकते हैं कि आपकोprep
. पर ट्रिगर की भी आवश्यकता है या नहीं तालिका का बिल्कुल संस्करण):CREATE TRIGGER live.trig_liveON live.t1फॉर INSERTASBEGIN SELECT i.id, msg ='live.trig' फ्रॉम इंसर्टेड फ्रॉम इन इनर जॉइन लाइव.t1 AS t ON i.id =t.id;ENDGOअब, हमारा नया स्कीमा स्वैप कैसे काम करेगा इसका एक त्वरित उदाहरण (और यदि आपके पास एकाधिक ट्रिगर हैं, तो आपको प्रत्येक ट्रिगर से निपटने के लिए इसे समायोजित करना पड़ सकता है, और इसे
prep
पर स्कीमा के लिए दोहराना होगा। संस्करण, यदि आपको वहां भी एक ट्रिगर बनाए रखने की आवश्यकता है। इस बात का विशेष ध्यान रखें कि संक्षिप्तता के लिए नीचे दिया गया कोड मानता है किlive.t1
पर केवल *एक* ट्रिगर है ।लेनदेन शुरू करें; DECLARE @ sql1 NVARCHAR (MAX), @ sql2 NVARCHAR (MAX); @ sql1 =N'DROP TRIGGER लाइव चुनें।' + QUOTENAME(नाम) + ';', @sql2 =OBJECT_DEFINITION([object_id]) sys.triggers से जहां [parent_id] =OBJECT_ID(N'live.t1'); EXEC sp_executesql @ sql1; -- स्थानांतरण से पहले ट्रिगर को छोड़ दें ALTER SCHEMA धारक TRANSFER prep.t1; ALTER SCHEMA प्रस्तुत करने का स्थानान्तरण live.t1; ALTER SCHEMA लाइव ट्रांसफ़र होल्डर.t1; EXEC sp_executesql @ sql2; -- ट्रांसफर कमिट ट्रांजैक्शन के बाद इसे फिर से बनाएं;एक और (कम वांछनीय) समाधान पूरे स्कीमा स्वैप ऑपरेशन को दो बार करना होगा, जिसमें
prep
के खिलाफ जो भी ऑपरेशन होते हैं, शामिल हैं। तालिका का संस्करण। जो मुख्य रूप से पहले स्थान पर स्कीमा स्वैप के उद्देश्य को विफल कर देता है:उस समय को कम करना जब उपयोगकर्ता तालिका (तालिकाओं) तक नहीं पहुंच सकते हैं और उन्हें न्यूनतम रुकावट के साथ अद्यतन डेटा लाते हैं।