วิธีเพิ่มประสิทธิภาพการแทรกใน PostgreSQL


216

ฉันกำลังทดสอบประสิทธิภาพการแทรก Postgres ฉันมีตารางที่มีหนึ่งคอลัมน์ที่มีตัวเลขเป็นชนิดข้อมูล มีดัชนีอยู่ด้วยเช่นกัน ฉันเติมฐานข้อมูลโดยใช้แบบสอบถามนี้:

insert into aNumber (id) values (564),(43536),(34560) ...

ฉันแทรก 4 ล้านแถวอย่างรวดเร็ว 10,000 ต่อครั้งพร้อมกับแบบสอบถามข้างต้น หลังจากฐานข้อมูลถึง 6 ล้านแถวประสิทธิภาพลดลงอย่างมากถึง 1 ล้านแถวทุก 15 นาที มีเคล็ดลับในการเพิ่มประสิทธิภาพการแทรกหรือไม่ ฉันต้องการประสิทธิภาพการแทรกที่ดีที่สุดในโครงการนี้

การใช้ Windows 7 Pro บนเครื่องที่มี RAM 5 GB


5
เป็นมูลค่าการกล่าวถึงรุ่น Pg ของคุณด้วยคำถาม ในกรณีนี้มันไม่ได้สร้างความแตกต่างมากมาย แต่มันทำเพื่อคำถามมากมาย
Craig Ringer

1
วางดัชนีบนตารางและทริกเกอร์ถ้ามีและเรียกใช้สคริปต์แทรก เมื่อคุณโหลดจำนวนมากเสร็จแล้วคุณสามารถสร้างดัชนีใหม่ได้
Sandeep

คำตอบ:


481

ดูเติมฐานข้อมูลในคู่มือ PostgreSQL บทความ depesz ยอดเยี่ยมตามปกติในหัวข้อและคำถาม SOนี้

(หมายเหตุว่าคำตอบนี้เป็นเรื่องเกี่ยวกับข้อมูลจำนวนมากในการโหลดลงในฐานข้อมูลที่มีอยู่หรือสร้างขึ้นมาใหม่. หากคุณสนใจ DB คืนค่าประสิทธิภาพการทำงานกับpg_restoreหรือpsqlการดำเนินการของpg_dumpการส่งออกมากนี้ใช้ไม่ได้ตั้งแต่pg_dumpและpg_restoreแล้วทำสิ่งต่างๆเช่นการสร้าง ทริกเกอร์และดัชนีหลังจากเสร็จสิ้นคีข้อมูล + เรียกคืน)

มีหลายสิ่งที่ต้องทำ ทางออกที่ดีที่สุดคือการนำเข้าสู่UNLOGGEDตารางโดยไม่มีดัชนีจากนั้นเปลี่ยนเป็นบันทึกและเพิ่มดัชนี น่าเสียดายที่ PostgreSQL 9.4 ไม่มีการสนับสนุนการเปลี่ยนตารางจากUNLOGGEDเป็นบันทึก 9.5 เพิ่มALTER TABLE ... SET LOGGEDเพื่ออนุญาตให้คุณทำเช่นนี้

pg_bulkloadหากคุณสามารถใช้ฐานข้อมูลออฟไลน์ของคุณสำหรับกลุ่มนำเข้าใช้

มิฉะนั้น:

  • ปิดการใช้งานทริกเกอร์ใด ๆ บนโต๊ะ

  • ปล่อยดัชนีก่อนเริ่มการนำเข้าสร้างใหม่อีกครั้งในภายหลัง (ใช้เวลาน้อยกว่ามากในการสร้างดัชนีในรอบเดียวมากกว่าที่จะเพิ่มข้อมูลเดียวกันในแบบก้าวหน้าและดัชนีผลลัพธ์จะมีขนาดเล็กกว่ามาก)

  • หากทำการนำเข้าภายในธุรกรรมเดียวจะปลอดภัยที่จะลดข้อ จำกัด ของคีย์ต่างประเทศดำเนินการนำเข้าและสร้างข้อ จำกัด ใหม่อีกครั้งก่อนส่งมอบ อย่าทำเช่นนี้หากการนำเข้าถูกแบ่งข้ามหลายธุรกรรมเนื่องจากคุณอาจแนะนำข้อมูลที่ไม่ถูกต้อง

  • ถ้าเป็นไปได้ให้ใช้COPYแทนINSERTs

  • หากคุณไม่สามารถใช้ลองCOPYใช้หลายค่าINSERTถ้าเป็นประโยชน์ คุณดูเหมือนจะทำสิ่งนี้แล้ว อย่าพยายามที่จะแสดงรายการเกินไปค่าจำนวนมากในครั้งเดียวVALUESแม้ว่า; ค่าเหล่านั้นต้องพอดีกับหน่วยความจำสองสามครั้งดังนั้นควรเก็บไว้ไม่กี่ร้อยต่อข้อความ

  • แบทช์แทรกของคุณลงในการทำธุรกรรมอย่างชัดเจนโดยการทำเม็ดมีดนับแสนหรือล้านรายการต่อธุรกรรม ไม่มีขีด จำกัด ในทางปฏิบัติ AFAIK แต่การแบทช์จะช่วยให้คุณกู้คืนจากข้อผิดพลาดโดยทำเครื่องหมายจุดเริ่มต้นของแต่ละแบทช์ในข้อมูลอินพุตของคุณ อีกครั้งคุณดูเหมือนจะทำสิ่งนี้อยู่แล้ว

  • ใช้งานsynchronous_commit=offและมีขนาดใหญ่commit_delayเพื่อลดค่าใช้จ่าย fsync () สิ่งนี้จะไม่ช่วยอะไรมากหากคุณทำแบทช์ของคุณเป็นธุรกรรมขนาดใหญ่

  • INSERTหรือCOPYขนานจากการเชื่อมต่อที่หลากหลาย จำนวนขึ้นอยู่กับระบบย่อยดิสก์ของฮาร์ดแวร์ โดยทั่วไปแล้วคุณต้องการการเชื่อมต่อหนึ่งตัวต่อฮาร์ดไดรฟ์จริงหากใช้ที่เก็บข้อมูลที่แนบโดยตรง

  • ตั้งสูงค่าและเปิดใช้งานcheckpoint_segments log_checkpointsดูบันทึก PostgreSQL และตรวจสอบให้แน่ใจว่าไม่ได้บ่นเกี่ยวกับจุดตรวจที่เกิดขึ้นบ่อยเกินไป

  • หากว่าคุณไม่สูญเสียคลัสเตอร์ PostgreSQL ทั้งหมดของคุณ (ฐานข้อมูลของคุณและอื่น ๆ ในคลัสเตอร์เดียวกัน) เพื่อให้เกิดความเสียหายอย่างรุนแรงหากระบบล่มระหว่างการนำเข้าคุณสามารถหยุด Pg, ตั้งค่าfsync=off, เริ่ม Pg, นำเข้าของคุณ จากนั้น (สำคัญ) หยุด Pg และตั้งfsync=onอีกครั้ง ดูการกำหนดค่า WAL อย่าทำเช่นนี้หากมีข้อมูลใด ๆ ที่คุณสนใจในฐานข้อมูลใด ๆ ในการติดตั้ง PostgreSQL ของคุณ หากคุณตั้งfsync=offคุณสามารถตั้งค่าfull_page_writes=off; อีกครั้งอย่าลืมเปิดอีกครั้งหลังจากนำเข้าของคุณเพื่อป้องกันความเสียหายของฐานข้อมูลและการสูญหายของข้อมูล ดูการตั้งค่าที่ไม่คงทนในคู่มือ Pg

คุณควรดูที่การปรับระบบของคุณ:

  • ใช้SSD คุณภาพดีสำหรับการจัดเก็บมากที่สุด SSD ที่ดีพร้อมแคชแคชแบ็คเขียนกลับที่เชื่อถือได้และมีอัตราการส่งข้อมูลที่รวดเร็วยิ่งขึ้น มันมีประโยชน์น้อยกว่าเมื่อคุณทำตามคำแนะนำด้านบนซึ่งช่วยลดจำนวนฟลัชดิสก์ / จำนวนfsync()s - แต่ก็ยังสามารถช่วยได้มาก อย่าใช้ SSD ราคาถูกโดยไม่มีการป้องกันความล้มเหลวของพลังงานอย่างเหมาะสมเว้นแต่คุณจะไม่สนใจเก็บข้อมูลของคุณ

  • หากคุณใช้ RAID 5 หรือ RAID 6 สำหรับที่เก็บข้อมูลที่เชื่อมต่อโดยตรงให้หยุดทันที สำรองข้อมูลของคุณปรับโครงสร้างอาร์เรย์ RAID ของคุณเป็น RAID 10 แล้วลองอีกครั้ง RAID 5/6 นั้นสิ้นหวังสำหรับประสิทธิภาพการเขียนจำนวนมาก - แม้ว่าตัวควบคุม RAID ที่ดีที่มีแคชขนาดใหญ่สามารถช่วยได้

  • หากคุณมีตัวเลือกในการใช้ตัวควบคุม RAID ของฮาร์ดแวร์ที่มีแคชการเขียนสำรองข้อมูลขนาดใหญ่ซึ่งจะช่วยปรับปรุงประสิทธิภาพการเขียนสำหรับเวิร์กโหลดด้วยภาระผูกพันจำนวนมาก มันไม่ได้ช่วยอะไรมากนักถ้าคุณใช้ async กระทำกับ commit_delay หรือถ้าคุณทำธุรกรรมใหญ่น้อยลงระหว่างการโหลดจำนวนมาก

  • หากเป็นไปได้ให้เก็บ WAL ( pg_xlog) ไว้ในดิสก์ / ดิสก์อาร์เรย์ต่างหาก มีจุดเล็กน้อยในการใช้ระบบไฟล์แยกต่างหากบนดิสก์เดียวกัน ผู้คนมักเลือกใช้คู่ RAID1 สำหรับ WAL อีกครั้งสิ่งนี้มีผลต่อระบบที่มีอัตราการส่งข้อมูลสูงและจะมีผลเพียงเล็กน้อยหากคุณใช้ตารางที่ไม่ถูกบล็อกเป็นเป้าหมายการโหลดข้อมูล

นอกจากนี้คุณยังอาจจะสนใจในการเพิ่มประสิทธิภาพ PostgreSQL สำหรับการทดสอบอย่างรวดเร็ว


1
คุณเห็นด้วยหรือไม่ว่าการลงโทษการเขียนจาก RAID 5/6 ค่อนข้างจะลดลงหากใช้ SSD คุณภาพดี เห็นได้ชัดว่ายังมีบทลงโทษ แต่ฉันคิดว่าความแตกต่างนั้นเจ็บปวดน้อยกว่าเมื่อเทียบกับ HDD

1
ฉันยังไม่ได้ทดสอบ ฉันจะบอกว่ามันอาจจะแย่กว่านี้ - เอฟเฟกต์การเขียนที่น่ารังเกียจและ (สำหรับการเขียนขนาดเล็ก) ยังต้องการวงจรอ่าน - แก้ไข - เขียนอยู่ แต่บทลงโทษที่รุนแรงสำหรับการค้นหาที่มากเกินไปควรไม่ใช่ประเด็น
Craig Ringer

เราสามารถปิดการใช้งานดัชนีแทนการปล่อยตัวอย่างเช่นโดยการตั้งค่าindisvalid( postgresql.org/docs/8.3/static/catalog-pg-index.html ) เป็นเท็จแล้วโหลดข้อมูลแล้วนำดัชนีออนไลน์ด้วยREINDEXหรือไม่
Vladislav Rastrusny

1
@CraigRinger ฉันได้ทดสอบ RAID-5 vs RAID-10 กับ SSD ของ Perc H730 RAID-5 เร็วกว่าจริงๆ นอกจากนี้อาจเป็นเรื่องน่าสังเกตว่าการแทรก / การทำธุรกรรมร่วมกับไบต์ขนาดใหญ่ดูเหมือนจะเร็วกว่าการคัดลอก คำแนะนำที่ดีโดยรวมว่า
atlaste

2
ทุกคนเห็นการปรับปรุงความเร็วที่สำคัญด้วยUNLOGGEDหรือไม่ การทดสอบอย่างรวดเร็วแสดงให้เห็นถึงการปรับปรุง 10-20%
serg

15

ใช้COPY table TO ... WITH BINARYซึ่งเป็นไปตามเอกสารประกอบคือ " ค่อนข้างเร็วกว่ารูปแบบข้อความและ CSV " ทำเช่นนี้ต่อเมื่อคุณมีหลายล้านแถวที่จะแทรกและหากคุณคุ้นเคยกับข้อมูลไบนารี

นี่คือสูตรตัวอย่างเช่นในหลามใช้ psycopg2 ด้วยการป้อนข้อมูลไบนารี


1
โหมดไบนารีสามารถประหยัดเวลาได้มากในอินพุตบางอย่างเช่นการประทับเวลาซึ่งการแยกวิเคราะห์เป็นแบบไม่สำคัญ สำหรับข้อมูลหลายประเภทมันไม่ได้ให้ประโยชน์มากนักหรืออาจช้าลงเล็กน้อยเนื่องจากแบนด์วิดท์ที่เพิ่มขึ้น จุดดียกมัน
Craig Ringer

11

นอกเหนือจากการโพสต์ของ Craig Ringer ที่ยอดเยี่ยมและการโพสต์บล็อกของ depesz หากคุณต้องการเพิ่มความเร็วในการแทรกของคุณผ่านทางส่วนต่อประสานODBC ( psqlodbc ) โดยใช้การแทรกคำสั่งที่เตรียมไว้ในการทำธุรกรรม ทำงานเร็ว:

  1. ตั้งค่าระดับการย้อนกลับเมื่อข้อผิดพลาดเป็น "ธุรกรรม" โดยระบุProtocol=-1ในสตริงการเชื่อมต่อ โดยค่าเริ่มต้น psqlodbc ใช้ระดับ "งบ" ซึ่งสร้าง SAVEPOINT สำหรับแต่ละคำสั่งแทนที่จะทำธุรกรรมทั้งหมดทำให้การแทรกช้าลง
  2. ใช้คำสั่งที่จัดเตรียมฝั่งเซิร์ฟเวอร์โดยการระบุUseServerSidePrepare=1ในสตริงการเชื่อมต่อ หากไม่มีตัวเลือกนี้ไคลเอ็นต์จะส่งคำสั่งแทรกทั้งหมดพร้อมกับแต่ละแถวที่ถูกแทรก
  3. ปิดใช้งานการยอมรับอัตโนมัติในแต่ละคำสั่งโดยใช้ SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
  4. SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);เมื่อแถวทั้งหมดได้รับการแทรกกระทำธุรกรรมโดยใช้ ไม่จำเป็นต้องเปิดธุรกรรมอย่างชัดเจน

น่าเสียดายที่ psqlodbc "ใช้งาน" SQLBulkOperationsโดยการออกชุดคำสั่งแทรกที่ไม่ได้เตรียมไว้เพื่อให้การแทรกเร็วที่สุดจำเป็นต้องเขียนโค้ดตามขั้นตอนข้างต้นด้วยตนเอง


ขนาดบัฟเฟอร์ซ็อกเก็ตขนาดใหญ่A8=30000000ในสตริงการเชื่อมต่อก็ควรใช้เพื่อเพิ่มความเร็วในการแทรก
Andrus

10

วันนี้ฉันใช้เวลาประมาณ 6 ชั่วโมงในเรื่องเดียวกัน ส่วนแทรกจะมีความเร็ว 'ปกติ' (น้อยกว่า 3sec ต่อ 100K) จนถึง 5MI (จาก 30MI ทั้งหมด) แถวจากนั้นประสิทธิภาพจะลดลงอย่างมาก (จนถึง 1 นาทีต่อ 100K)

ฉันจะไม่เขียนรายการทุกสิ่งที่ไม่ได้ผลและตัดตรงเนื้อ

ฉันทิ้งคีย์หลักบนตารางเป้าหมาย (ซึ่งเป็น GUID) และ 30MI หรือแถวของฉันไหลอย่างมีความสุขไปยังจุดหมายปลายทางด้วยความเร็วคงที่น้อยกว่า 3 วินาทีต่อ 100K


7

หากคุณมีความสุขที่จะใส่ colums ด้วย UUIDs (ซึ่งไม่ใช่กรณีของคุณอย่างแน่นอน ) และเพื่อเพิ่ม @Dennis answer (ฉันยังไม่สามารถแสดงความคิดเห็นได้) ให้คำแนะนำมากกว่าการใช้ gen_random_uuid () (ต้องใช้ PG 9.4 และโมดูล pgcrypto) คือ ( มาก) เร็วกว่า uuid_generate_v4 ()

=# explain analyze select uuid_generate_v4(),* from generate_series(1,10000);
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=11.674..10304.959 rows=10000 loops=1)
 Planning time: 0.157 ms
 Execution time: 13353.098 ms
(3 filas)

VS


=# explain analyze select gen_random_uuid(),* from generate_series(1,10000);
                                                        QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=252.274..418.137 rows=10000 loops=1)
 Planning time: 0.064 ms
 Execution time: 503.818 ms
(3 filas)

นอกจากนี้ยังเป็นวิธีการแนะนำอย่างเป็นทางการที่จะทำ

บันทึก

หากคุณต้องการเพียง UUID ที่สร้างแบบสุ่มเท่านั้นให้พิจารณาใช้ฟังก์ชัน gen_random_uuid () จากโมดูล pgcrypto แทน

เวลาแทรกนี้ลดลงจาก ~ 2 ชั่วโมงถึง ~ 10 นาทีสำหรับแถว 3.7M


1

เพื่อประสิทธิภาพการแทรกที่ดีที่สุดให้ปิดการใช้งานดัชนีหากเป็นตัวเลือกสำหรับคุณ นอกจากนั้นฮาร์ดแวร์ที่ดีกว่า (ดิสก์หน่วยความจำ) ก็มีประโยชน์เช่นกัน


-1

ฉันพบปัญหาประสิทธิภาพการแทรกนี้เช่นกัน วิธีการแก้ปัญหาของฉันคือวางไข่บ้างเพื่อให้งานแทรกเสร็จ ในระหว่างนี้SetMaxOpenConnsควรได้รับหมายเลขที่เหมาะสมมิฉะนั้นจะมีการแจ้งเตือนข้อผิดพลาดในการเชื่อมต่อที่เปิดมากเกินไป

db, _ := sql.open() 
db.SetMaxOpenConns(SOME CONFIG INTEGER NUMBER) 
var wg sync.WaitGroup
for _, query := range queries {
    wg.Add(1)
    go func(msg string) {
        defer wg.Done()
        _, err := db.Exec(msg)
        if err != nil {
            fmt.Println(err)
        }
    }(query)
}
wg.Wait()

ความเร็วในการโหลดเร็วขึ้นมากสำหรับโครงการของฉัน ตัวอย่างรหัสนี้เพิ่งให้แนวคิดว่ามันทำงานอย่างไร ผู้อ่านควรสามารถแก้ไขได้อย่างง่ายดาย


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