วิธีที่ดีที่สุดในการเติมคอลัมน์ใหม่ในตารางขนาดใหญ่?


33

เรามีตาราง 2.2 GB ใน Postgres ที่มี 7,801,611 แถว เรากำลังเพิ่มคอลัมน์ uuid / guid ลงไปและฉันสงสัยว่าวิธีที่ดีที่สุดในการเติมข้อมูลคอลัมน์นั้นคืออะไร (ตามที่เราต้องการเพิ่มNOT NULLข้อ จำกัด )

หากฉันเข้าใจ Postgres อย่างถูกต้องการอัปเดตเป็นเทคนิคลบและแทรกดังนั้นนี่คือการสร้างตาราง 2.2 gb ใหม่ทั้งหมด นอกจากนี้เรายังมีทาสวิ่งอยู่ดังนั้นเราจึงไม่ต้องการให้มันล้าหลัง

มีวิธีใดที่ดีไปกว่าการเขียนสคริปต์ที่ค่อยๆเติมมันลงไปตามกาลเวลา?


2
คุณเคยใช้งานALTER TABLE .. ADD COLUMN ...หรือมีส่วนที่จะตอบด้วยหรือไม่?
ypercubeᵀᴹ

ยังไม่ได้ทำการปรับเปลี่ยนตารางใด ๆ เพียงแค่อยู่ในขั้นตอนการวางแผน ฉันทำสิ่งนี้มาก่อนโดยการเพิ่มคอลัมน์เติมข้อมูลจากนั้นเพิ่มข้อ จำกัด หรือดัชนี อย่างไรก็ตามตารางนี้อย่างมีนัยสำคัญที่ใหญ่กว่าและฉันกำลังกังวลเกี่ยวกับการโหลดล็อคจำลอง ฯลฯ ...
Collin ปีเตอร์ส

คำตอบ:


45

มันขึ้นอยู่กับรายละเอียดความต้องการของคุณเป็นอย่างมาก

หากคุณมีพื้นที่ว่างเพียงพอ (อย่างน้อย 110% ของpg_size_pretty((pg_total_relation_size(tbl))) บนดิสก์และสามารถจ่ายได้ล็อคหุ้นสำหรับบางเวลาและล็อคพิเศษสำหรับเวลาที่สั้นมากแล้วสร้างตารางใหม่รวมทั้งคอลัมน์โดยใช้uuid CREATE 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/ DELETEในตารางเดียวกันหลังจากที่การทำธุรกรรมของคุณได้ทำการSHAREล็อคแล้วจะรอจนกว่าการปลดล็อคจะเริ่มขึ้นหรือการหมดเวลาใช้งานจะเริ่มขึ้น พวกเขาจะล้มเหลวอย่างใดอย่างหนึ่งเนื่องจากตารางที่พวกเขาพยายามที่จะเขียนถูกลบออกจากใต้พวกเขา

ตารางใหม่มี OID ของตารางใหม่ แต่ธุรกรรมที่เกิดขึ้นพร้อมกันได้แก้ไขชื่อของตารางไปเป็น OID ของตารางก่อนหน้านี้แล้ว เมื่อล็อคถูกปล่อยออกมาในที่สุดพวกเขาพยายามที่จะล็อคโต๊ะตัวเองก่อนที่จะเขียนลงไปและพบว่ามันหายไป Postgres จะตอบ:

ERROR: could not open relation with OID 123456

123456OID ของตารางเก่าอยู่ที่ไหน คุณต้องตรวจสอบข้อยกเว้นนั้นและลองค้นหาในรหัสแอปของคุณอีกครั้งเพื่อหลีกเลี่ยง

หากคุณไม่สามารถซื้อสิ่งนั้นได้คุณต้องเก็บตารางดั้งเดิมไว้

สองทางเลือกในการรักษาตารางที่มีอยู่

  1. อัปเดตในสถานที่ (อาจเรียกใช้การอัปเดตในส่วนเล็ก ๆ ในเวลา) ก่อนที่คุณจะเพิ่มNOT NULLข้อ จำกัด การเพิ่มคอลัมน์ใหม่ด้วยค่า NULL และไม่มีNOT NULLข้อ จำกัด นั้นราคาถูก
    ตั้งแต่ Postgres 9.2คุณสามารถสร้างCHECKข้อ จำกัด ด้วยNOT 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ต้นฉบับและเติมเงินจากตาราง temp ทั้งหมดในการทำธุรกรรม คุณยังต้องใช้การSHAREล็อค ก่อนจัดทำตารางใหม่เพื่อป้องกันการสูญเสียการเขียนพร้อมกัน

    รายละเอียดในคำตอบที่เกี่ยวข้องเหล่านี้ใน SO:


คำตอบที่ยอดเยี่ยม! ข้อมูลที่ฉันต้องการ คำถามสองข้อ 1. คุณมีความคิดใด ๆ ในวิธีง่าย ๆ ในการทดสอบว่าการกระทำแบบนี้จะต้องใช้เวลานานแค่ไหน? 2. หากใช้เวลาประมาณ 5 นาทีจะเกิดอะไรขึ้นกับการกระทำที่พยายามอัปเดตแถวในตารางนั้นในช่วง 5 นาทีเหล่านั้น
Collin Peters

@CollinPeters: 1. ส่วนแบ่งของเวลาที่จะไปคัดลอกตารางขนาดใหญ่ - และอาจสร้างดัชนีและข้อ จำกัด ใหม่ (ขึ้นอยู่กับ) การวางและการเปลี่ยนชื่อมีราคาถูก ในการทดสอบคุณสามารถเรียกใช้สคริปต์ SQL ของคุณที่เตรียมไว้โดยไม่ต้องขึ้นไปและไม่รวมLOCK DROPฉันทำได้แค่คาดเดาป่าและไร้ประโยชน์ สำหรับ 2. โปรดพิจารณาภาคผนวกของคำตอบของฉัน
Erwin Brandstetter

@ErwinBrandstetter ดำเนินการต่อในการสร้างมุมมองใหม่ดังนั้นถ้าฉันมีมุมมองโหลที่ยังคงใช้ตารางเก่า (oid) หลังจากเปลี่ยนชื่อตาราง มีวิธีใดที่จะทำการแทนที่แบบลึกแทนที่จะรันการรีเฟรช / การสร้างมุมมองทั้งหมดอีกครั้ง?
CodeFarmer

@CodeFarmer: หากคุณเพิ่งเปลี่ยนชื่อตารางมุมมองจะทำงานกับตารางที่เปลี่ยนชื่อ ในการทำให้มุมมองใช้ตารางใหม่แทนคุณจะต้องสร้างมุมมองใหม่ตามตารางใหม่ (เพื่อให้สามารถลบตารางเก่าได้) ไม่มีวิธี (ปฏิบัติ) รอบตัว
Erwin Brandstetter

14

ฉันไม่มีคำตอบที่ "ดีที่สุด" แต่ฉันมีคำตอบที่ "แย่ที่สุด" ซึ่งอาจทำให้คุณทำงานได้เร็วพอสมควร

ตารางของฉันมีแถว 2 มม. และประสิทธิภาพการอัปเดตถูกตรวจสอบเมื่อฉันพยายามเพิ่มคอลัมน์การประทับเวลารองที่เริ่มต้นเป็นคอลัมน์แรก

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 วางไว้ ฉันต้องการหลีกเลี่ยงการสลับคีย์ต่างประเทศในตารางจำนวนมาก แล้วมีมุมมอง

บิตของการค้นหาเอกสารกรณีศึกษาและ StackOverflow และฉันมี "A-Ha!" ขณะ ท่อระบายน้ำไม่ได้อยู่ใน Core UPDATE แต่ในการดำเนินการ INDEX ทั้งหมด ตารางของฉันมีดัชนี 12 ดัชนี - ข้อ จำกัด ที่ไม่ซ้ำกันและข้อ จำกัด บางประการสำหรับการเพิ่มความเร็วในการวางแผนแบบสอบถามและอีกเล็กน้อยสำหรับการค้นหาแบบเต็ม

ทุกแถวที่อัปเดตไม่เพียง แต่ทำงานบน DELETE / INSERT เท่านั้น แต่ยังรวมถึงค่าใช้จ่ายในการเปลี่ยนแปลงแต่ละดัชนีและการตรวจสอบข้อ จำกัด

โซลูชันของฉันคือการวางดัชนีและข้อ จำกัด ทุกตัวอัปเดตตารางจากนั้นเพิ่มดัชนี / ข้อ จำกัด ทั้งหมดกลับเข้ามา

ใช้เวลาประมาณ 3 นาทีในการเขียนธุรกรรม SQL ที่ทำสิ่งต่อไปนี้:

  • เริ่ม;
  • ลดลงดัชนี / constaints
  • ตารางการอัพเดท
  • เพิ่มดัชนี / ข้อ จำกัด อีกครั้ง
  • COMMIT;

สคริปต์ใช้เวลารัน 7 นาที

คำตอบที่ได้รับการยอมรับนั้นดีกว่าและเหมาะสมกว่า ... และไม่จำเป็นต้องหยุดทำงาน ในกรณีของฉันมันจะใช้งานมากขึ้น "นักพัฒนา" เพื่อใช้โซลูชันนั้นและเรามีหน้าต่าง 30 นาทีของการหยุดทำงานตามกำหนดเวลาที่สามารถทำได้ระบบของเราแก้ปัญหาได้ใน 10

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.