एक बड़ी तालिका में एक नए कॉलम को आबाद करने का सबसे अच्छा तरीका है?


33

हमारे पास Postgres में एक 2.2 GB टेबल है जिसमें 7,801,611 पंक्तियाँ हैं। हम इसमें एक uuid / गाइड कॉलम जोड़ रहे हैं और मैं सोच रहा हूं कि उस कॉलम को आबाद करने का सबसे अच्छा तरीका क्या है (जैसा कि हम NOT NULLइसमें एक बाधा जोड़ना चाहते हैं )।

अगर मुझे समझ में आता है कि पोस्टग्रैज सही ढंग से अपडेट होता है तो तकनीकी रूप से डिलीट और इन्सर्ट हो जाता है, इसलिए यह मूल रूप से संपूर्ण 2.2 gb टेबल का पुनर्निर्माण कर रहा है। इसके अलावा, हमारे पास एक दास चल रहा है इसलिए हम नहीं चाहते कि वह पिछड़ जाए।

क्या स्क्रिप्ट लिखने से बेहतर कोई तरीका है जो समय के साथ धीरे-धीरे इसे पॉप्युलेट करता है?


2
क्या आप पहले ही भाग चुके हैं ALTER TABLE .. ADD COLUMN ...या इसका उत्तर भी दिया जा सकता है?
ypercube y

अभी तक कोई तालिका संशोधन नहीं किया है, सिर्फ योजना चरण में। मैंने कॉलम को जोड़कर, उसे पॉप्युलेट करने से पहले, फिर बाधा या सूचकांक को जोड़कर ऐसा किया है। हालाँकि, यह तालिका काफी बड़ी है और मैं लोड, लॉकिंग, प्रतिकृति, आदि के बारे में चिंतित हूं ...
Collin पीटर्स

जवाबों:


45

यह बहुत कुछ आपकी आवश्यकताओं के विवरण पर निर्भर करता है।

यदि आपके पास डिस्क पर पर्याप्त खाली स्थान (कम से कम 110% pg_size_pretty((pg_total_relation_size(tbl))) है और कुछ समय के लिए एक शेयर लॉक और बहुत कम समय के लिए अनन्य लॉक का खर्च वहन कर सकते हैं , तो उपयोग करके कॉलम सहित एक नई तालिका बनाएं । क्यूं कर?uuidCREATE TABLE AS

नीचे दिया गया कोड अतिरिक्त uuid-ossमॉड्यूल से एक फ़ंक्शन का उपयोग करता है ।

  • SHAREमोड में समवर्ती परिवर्तनों के खिलाफ तालिका को लॉक करें (फिर भी समवर्ती रीड की अनुमति देता है)। तालिका में लिखने का प्रयास प्रतीक्षा करेगा और अंततः विफल हो जाएगा। निचे देखो।

  • मक्खी पर नए कॉलम को आबाद करते समय पूरी तालिका की प्रतिलिपि बनाएँ - संभवतः पंक्तियों को अनुकूल बनाते हुए उस पर रहते हुए।
    यदि आप पंक्तियों को फिर से व्यवस्थित करने जा रहे हैं, तो सुनिश्चित करें कि work_memआप जितना अधिक खर्च कर सकते हैं (बस अपने सत्र के लिए, विश्व स्तर पर नहीं)।

  • फिर नई तालिका में बाधाओं, विदेशी कुंजियों, सूचकांकों, ट्रिगर आदि को जोड़ें। तालिका के बड़े हिस्से को अपडेट करते समय पंक्तियों को जोड़ने के लिए खरोंच से सूचकांक बनाने के लिए यह बहुत तेज़ होता है।

  • जब नई तालिका तैयार हो जाती है, तो पुराने को छोड़ दें और नया नाम बदलकर इसे ड्रॉप-इन प्रतिस्थापन करें। केवल यह अंतिम चरण पुराने लेन-देन के बाकी हिस्सों के लिए एक विशेष लॉक प्राप्त करता है - जो अभी बहुत कम होना चाहिए।
    इसके लिए यह भी आवश्यक है कि आप तालिका प्रकार (विचार, कार्य हस्ताक्षर में तालिका प्रकार का उपयोग करके ...) के आधार पर किसी भी ऑब्जेक्ट को हटा दें और उन्हें फिर से बनाएँ।

  • अधूरे राज्यों से बचने के लिए यह सब एक लेनदेन में करें।

BEGIN;
LOCK TABLE tbl IN SHARE MODE;

SET LOCAL work_mem = '???? MB';  -- just for this transaction

CREATE TABLE tbl_new AS 
SELECT uuid_generate_v1() AS tbl_uuid, <list of all columns in order>
FROM   tbl
ORDER  BY ??;  -- optionally order rows favorably while being at it.

ALTER TABLE tbl_new
   ALTER COLUMN tbl_uuid SET NOT NULL
 , ALTER COLUMN tbl_uuid SET DEFAULT uuid_generate_v1()
 , ADD CONSTRAINT tbl_uuid_uni UNIQUE(tbl_uuid);

-- more constraints, indices, triggers?

DROP TABLE tbl;
ALTER TABLE tbl_new RENAME tbl;

-- recreate views etc. if any
COMMIT;

यह सबसे तेज होना चाहिए। जगह में अद्यतन करने के किसी भी अन्य तरीके से पूरी मेज को फिर से लिखना पड़ता है, बस अधिक महंगा फैशन में। यदि आप डिस्क पर पर्याप्त खाली स्थान नहीं रखते हैं या आप संपूर्ण तालिका को लॉक नहीं कर सकते हैं या समवर्ती लेखन प्रयासों के लिए त्रुटियों को उत्पन्न नहीं कर सकते हैं तो आप केवल उस मार्ग पर जाएंगे।

क्या होता है समवर्ती लेखन?

अन्य लेन-देन (अन्य सत्रों में) आपके लेनदेन को लॉक करने के बाद उसी तालिका में INSERT/ UPDATE/ की कोशिश कर रहा है , तब तक इंतजार करेगा जब तक कि लॉक जारी न हो जाए या कोई टाइमआउट किक न करे, जो भी पहले आए। वे असफल हो जाएंगेDELETESHARE किसी भी तरह , क्योंकि जिस तालिका को वे लिखने की कोशिश कर रहे थे, उसके तहत से हटा दिया गया है।

नई तालिका में एक नई तालिका OID है, लेकिन समवर्ती लेन-देन ने पहले ही तालिका का नाम पिछले तालिका के OID को हल कर दिया है । जब अंत में लॉक जारी किया जाता है, तो वे इसे लिखने से पहले टेबल को खुद लॉक करने की कोशिश करते हैं और पाते हैं कि यह चला गया है। उत्तर का जवाब देंगे:

ERROR: could not open relation with OID 123456

123456पुरानी तालिका का ओआईडी कहां है। इससे बचने के लिए आपको उस अपवाद को पकड़ने और अपने ऐप कोड में प्रश्नों को पुनः प्राप्त करने की आवश्यकता है।

यदि आप ऐसा नहीं कर सकते, तो आपको रखना होगा अपनी मूल तालिका ।

मौजूदा तालिका को ध्यान में रखते हुए दो विकल्प

  1. NOT NULLबाधा डालने से पहले स्थान में अद्यतन (संभवतः एक समय में छोटे खंडों में अद्यतन चलाना) । NULL मान के साथ और बिना NOT NULLबाधा के एक नया स्तंभ जोड़ना सस्ता है। 9.2 के
    बाद से आप इसके साथ एक बाधा भी बना सकते हैं :CHECKNOT VALID

    बाधा अभी भी आवेषण या अपडेट के खिलाफ लागू की जाएगी

    यही कारण है कि अद्यतन पंक्तियों करने की अनुमति देता peu à peu में - अधिक अलग-अलग लेन-देन । यह पंक्ति को लंबे समय तक लॉक रखने से बचता है और यह मृत पंक्तियों को पुन: उपयोग करने की अनुमति देता है। ( VACUUMयदि ऑटोवैक्युम को किक करने के लिए बीच में पर्याप्त समय नहीं है, तो आपको मैन्युअल रूप से चलना होगा।) अंत में, NOT NULLबाधा जोड़ें और बाधा को हटा दें NOT VALID CHECK:

    ALTER TABLE tbl ADD CONSTRAINT tbl_no_null CHECK (tbl_uuid IS NOT NULL) NOT VALID;
    
    -- update rows in multiple batches in separate transactions
    -- possibly run VACUUM between transactions
    
    ALTER TABLE tbl ALTER COLUMN tbl_uuid SET NOT NULL;
    ALTER TABLE tbl ALTER DROP CONSTRAINT tbl_no_null;

    NOT VALIDअधिक विस्तार से संबंधित संबंधित उत्तर :

  2. एक में नए राज्य तैयार अस्थायी तालिका , TRUNCATEमूल और रीफिल अस्थायी मेज से। सभी एक लेन-देन में । समवर्ती लेखन को खोने से रोकने के लिए नई तालिका तैयार करने से पहले आपको अभी भी एक SHAREताला लगाने की आवश्यकता है ।

    एसओ पर इन संबंधित उत्तर में विवरण:


शानदार जवाब! बिल्कुल वही जानकारी जिसकी मुझे तलाश थी। दो प्रश्न 1. क्या आपको इस बात का परीक्षण करने का एक आसान तरीका है कि इस तरह की कार्रवाई में कितना समय लगेगा? 2. यदि यह 5 मिनट का समय लेता है, तो उन 5 मिनटों के दौरान उस तालिका में एक पंक्ति को अपडेट करने की कोशिश करने वाली क्रियाओं का क्या होता है?
कोलिन पीटर्स

@CollinPeters: 1. शेर का समय का हिस्सा बड़ी तालिका की नकल करने में जाएगा - और संभवतः सूचकांकों और बाधाओं (जो निर्भर करता है) को फिर से बनाएगा। गिराना और नाम बदलना सस्ता है। परीक्षण करने के लिए आप अपनी तैयार की गई एसक्यूएल स्क्रिप्ट को बिना LOCKऔर बाहर किए चला सकते हैं DROP। मैं केवल जंगली और बेकार अनुमान लगा सकता था। 2 के लिए, कृपया मेरे उत्तर के लिए परिशिष्ट पर विचार करें।
इरविन ब्रान्डस्टेट्टर

@ErwinBrandstetter रीक्रिएट व्यू पर जारी रखें, इसलिए यदि मेरे पास एक दर्जन से अधिक दृश्य हैं जो अभी भी पुराने नाम (oid) को टेबल रिनेम करने के बाद उपयोग करते हैं। क्या पूरे दृश्य को ताज़ा / बनाने के बजाय फिर से गहरी जगह बनाने का कोई तरीका है?
कोडफार्मर

@CodeFarmer: यदि आप सिर्फ एक तालिका का नाम बदलते हैं, तो नाम बदल दी गई तालिका के साथ काम करते रहते हैं। इसके बजाय नए तालिका का उपयोग करने के लिए , आपको नई तालिका के आधार पर उन्हें फिर से बनाने की आवश्यकता है। (पुरानी तालिका को हटाने की अनुमति देने के लिए भी।) इसके आसपास कोई (व्यावहारिक) तरीका नहीं।
इरविन ब्रान्डस्टेट्टर

14

मेरे पास "सर्वश्रेष्ठ" उत्तर नहीं है, लेकिन मेरे पास एक "कम से कम" उत्तर है जो आपको चीजों को यथोचित तेजी से प्राप्त करने की अनुमति दे सकता है।

मेरी तालिका में 2MM पंक्तियाँ थीं और जब मैं पहली बार डिफ़ॉल्ट सेकेंडरी टाइमस्टैम्प कॉलम को जोड़ने का प्रयास कर रहा था, तो अपडेट का प्रदर्शन कम था।

ALTER TABLE mytable ADD new_timestamp TIMESTAMP ;
UPDATE mytable SET new_timestamp = old_timestamp ;
ALTER TABLE mytable ALTER new_timestamp SET NOT NULL ;

इसके 40 मिनट तक लटकाए रखने के बाद, मैंने एक छोटे से बैच पर यह कोशिश की कि यह अंदाजा लगाया जा सकता है कि इसमें कितना समय लग सकता है - पूर्वानुमान लगभग 8 घंटे का था।

स्वीकृत उत्तर निश्चित रूप से बेहतर है - लेकिन यह तालिका मेरे डेटाबेस में बहुत अधिक उपयोग की जाती है। कुछ दर्जन टेबल हैं जो उस पर FKEY; मैं इतने सारे टेबल पर FOREIGN KEY को स्विच करने से बचना चाहता था। और फिर विचार हैं।

डॉक्स, केस-स्टडीज़ और स्टैकओवरफ़्लो की थोड़ी खोज, और मुझे "ए-हा!" पल। यह ड्रेन कोर UPDATE पर नहीं था, बल्कि सभी INDEX ऑपरेशंस पर था। मेरी तालिका में इस पर 12 सूचकांक थे - कुछ अनूठे अवरोधों के लिए, कुछ क्वेरी प्लानर को गति देने के लिए, और कुछ पूर्ण खोज के लिए।

हर पंक्ति जो अद्यतन की गई थी वह केवल एक DELETE / INSERT पर काम नहीं कर रही थी, बल्कि प्रत्येक सूचकांक को बदलने और बाधाओं की जाँच करने के लिए भी उपर थी।

मेरा समाधान हर सूचकांक और बाधा को छोड़ना था, तालिका को अपडेट करना था, फिर सभी सूचकांक / बाधाओं को वापस जोड़ना था।

SQL ट्रांजेक्शन को लिखने में लगभग 3 मिनट का समय लगा जो कि निम्नलिखित था:

  • शुरू;
  • अनुक्रमित / बाधाएं गिरा दी गईं
  • अद्यतन तालिका
  • अनुक्रमणिका / बाधाओं को पुनः जोड़ें
  • COMMIT;

स्क्रिप्ट को चलने में 7 मिनट का समय लगा।

स्वीकृत उत्तर निश्चित रूप से बेहतर और अधिक उचित है ... और वस्तुतः डाउनटाइम की आवश्यकता को समाप्त करता है। मेरे मामले में, हालांकि, उस समाधान का उपयोग करने के लिए इसे अधिक "डेवलपर" काम में लिया गया होगा और हमारे पास निर्धारित डाउनटाइम की 30 मिनट की खिड़की थी जिसे इसे पूरा किया जा सकता था। हमारे समाधान ने इसे 10 में संबोधित किया।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.