วิธีที่เร็วที่สุดในการแทรกจำนวนมากใน Postgres คืออะไร


242

ฉันต้องการแทรกระเบียน 10 ล้านรายการโดยทางโปรแกรมลงในฐานข้อมูล postgres ปัจจุบันฉันกำลังดำเนินการของคำสั่งแทรก 1,000 รายการใน "แบบสอบถาม" เดียว

มีวิธีที่ดีกว่าในการทำคำสั่งแทรกจำนวนมากที่ฉันไม่รู้หรือไม่?

คำตอบ:


211

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


33
ฉันเขียนรายละเอียดเพิ่มเติมเล็กน้อยเพื่ออธิบายเพิ่มเติมในstackoverflow.com/questions/12206600/
Craig Ringer

24
@ CraigRinger ว้าว "รายละเอียดอีกเล็กน้อย" เป็นคำพูดที่ดีที่สุดที่ฉันเคยเห็นมาตลอดทั้งสัปดาห์;)
culix

ลองติดตั้งแพคเกจ NpgsqlBulkCopy
Elyor

1
-Since index ยังถูกใช้สำหรับโครงร่างฟิสิคัลของเร็กคอร์ด db ไม่แน่ใจว่าการลบดัชนีในฐานข้อมูลใด ๆ เป็นความคิดที่ดีหรือไม่
Farjad

แต่คุณแนะนำไม่มีอะไรในหน่วยความจำ !!! และถ้าขนาดแบทช์ของคุณเป็นจำนวนน้อยงานแย่มากก็คือคลาส :( ฉันลองคลาส npgsql CopyIn เพราะมันเหมือนกับการจัดรูปแบบ CSV ในรูปแบบคำสั่งการสืบค้น PG คุณสามารถลองใช้ตารางขนาดใหญ่ได้ไหม
Elyor

94

มีทางเลือกอื่นในการใช้ COPY ซึ่งเป็นไวยากรณ์ค่า multirow ที่ Postgres รองรับ จากเอกสาร :

INSERT INTO films (code, title, did, date_prod, kind) VALUES
    ('B6717', 'Tampopo', 110, '1985-02-10', 'Comedy'),
    ('HG120', 'The Dinner Game', 140, DEFAULT, 'Comedy');

โค้ดด้านบนแทรกสองแถว แต่คุณสามารถขยายได้โดยพลการจนกว่าคุณจะถึงจำนวนโทเค็นของคำสั่งที่เตรียมไว้จำนวนสูงสุด (อาจเป็น $ 999 แต่ฉันไม่แน่ใจ 100% เกี่ยวกับเรื่องนี้) บางครั้งเราไม่สามารถใช้ COPY ได้และนี่เป็นการทดแทนที่คุ้มค่าสำหรับสถานการณ์เหล่านั้น


12
คุณรู้หรือไม่ว่าประสิทธิภาพของวิธีนี้เปรียบเทียบกับ COPY อย่างไร
ฮัมฟรีส์

หากคุณพบปัญหาการอนุญาตก่อนที่จะลองใช้ COPY ... จาก STDIN
Andrew Scott Evans เมื่อ

หากคุณใช้การรักษาความปลอดภัยระดับแถวนี่เป็นวิธีที่ดีที่สุดที่คุณสามารถทำได้ "COPY FROM ไม่ได้รับการสนับสนุนสำหรับตารางที่มีการรักษาความปลอดภัยระดับแถว" ตั้งแต่วันที่ 12
Eloff

COPY เร็วกว่า INSERT ที่ขยายมาก
hipertracker

24

วิธีหนึ่งในการเพิ่มความเร็วคือการแทรกหรือคัดลอกหลายรายการอย่างชัดเจนภายในธุรกรรม (พูด 1,000) พฤติกรรมเริ่มต้นของ Postgres คือการยอมรับหลังจากแต่ละคำสั่งดังนั้นโดยการแบทช์คอมมิทคุณสามารถหลีกเลี่ยงค่าใช้จ่ายบางอย่าง ตามคำแนะนำในคำตอบของ Daniel คุณอาจต้องปิดการใช้งาน autocommit เพื่อให้มันใช้งานได้ นอกจากนี้โปรดทราบความคิดเห็นที่ด้านล่างที่แนะนำการเพิ่มขนาดของ wal_buffers เป็น 16 MB อาจช่วยได้เช่นกัน


1
เป็นมูลค่าการกล่าวขวัญว่าขีด จำกัด ของจำนวนแทรก / สำเนาที่คุณสามารถเพิ่มในธุรกรรมเดียวกันนั้นมีแนวโน้มสูงกว่าสิ่งที่คุณพยายามทำ คุณสามารถเพิ่มแถวเป็นล้าน ๆ แถวภายในธุรกรรมเดียวกันและไม่พบปัญหา
Sumeet Jain

@SumeetJain ใช่ฉันเพิ่งพูดถึงความเร็ว 'จุดที่น่าสนใจ' ในแง่ของจำนวนสำเนา / ส่วนแทรกต่อการทำธุรกรรม
Dana the Sane

สิ่งนี้จะล็อคตารางในขณะที่ธุรกรรมกำลังทำงานอยู่หรือไม่
แลมบ์ดา Fairy

15

UNNESTฟังก์ชั่นที่มีอาร์เรย์สามารถใช้พร้อมกับไวยากรณ์หลายค่า ฉันคิดว่าวิธีนี้ช้ากว่าการใช้COPYแต่มันมีประโยชน์กับฉันในการทำงานกับ psycopg และ python (python listถูกส่งไปยังcursor.executepg ARRAY):

INSERT INTO tablename (fieldname1, fieldname2, fieldname3)
VALUES (
    UNNEST(ARRAY[1, 2, 3]), 
    UNNEST(ARRAY[100, 200, 300]), 
    UNNEST(ARRAY['a', 'b', 'c'])
);

โดยไม่ต้องVALUESใช้ตัวเลือกย่อยพร้อมกับการตรวจสอบเพิ่มเติม:

INSERT INTO tablename (fieldname1, fieldname2, fieldname3)
SELECT * FROM (
    SELECT UNNEST(ARRAY[1, 2, 3]), 
           UNNEST(ARRAY[100, 200, 300]), 
           UNNEST(ARRAY['a', 'b', 'c'])
) AS temptable
WHERE NOT EXISTS (
    SELECT 1 FROM tablename tt
    WHERE tt.fieldname1=temptable.fieldname1
);

ไวยากรณ์เดียวกันกับการอัปเดตจำนวนมาก:

UPDATE tablename
SET fieldname1=temptable.data
FROM (
    SELECT UNNEST(ARRAY[1,2]) AS id,
           UNNEST(ARRAY['a', 'b']) AS data
) AS temptable
WHERE tablename.id=temptable.id;

11

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

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


9

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

วิธีแรกของฉันคือ: สร้างตาราง (temp) ที่มีโครงสร้างคล้ายกับตารางเป้าหมาย (สร้างตาราง tmp AS select * จากเป้าหมายโดยที่ 1 = 0) และเริ่มต้นด้วยการอ่านไฟล์ลงในตาราง temp จากนั้นฉันจะตรวจสอบสิ่งที่สามารถตรวจสอบได้: รายการซ้ำ, คีย์ที่มีอยู่แล้วในเป้าหมาย ฯลฯ

จากนั้นฉันเพิ่งทำ "จะแทรกลงในเป้าหมาย select * จาก tmp" หรือคล้ายกัน

หากสิ่งนี้ล้มเหลวหรือใช้เวลานานเกินไปฉันจะยกเลิกและพิจารณาวิธีอื่น ๆ (วางดัชนี / ข้อ จำกัด ชั่วคราวไว้ชั่วคราว)



6

ฉันเพิ่งพบปัญหานี้และจะแนะนำcsvsql ( รุ่น ) สำหรับการนำเข้าจำนวนมากไปยัง Postgres เพื่อทำการแทรกจำนวนมากที่คุณต้องการcreatedbใช้csvsqlซึ่งจะเชื่อมต่อกับฐานข้อมูลของคุณและสร้างตารางแยกต่างหากสำหรับทั้งโฟลเดอร์ของ CSV

$ createdb test 
$ csvsql --db postgresql:///test --insert examples/*.csv

1
สำหรับ csvsql เพื่อที่จะทำความสะอาดยัง CSV ที่มาจากข้อผิดพลาดการจัดรูปแบบที่เป็นไปได้ก็จะดีที่สุดที่จะปฏิบัติตามคำแนะนำเหล่านี้เอกสารอื่น ๆที่นี่
พะยอม

0

ไฟล์ภายนอกที่ดีที่สุดและเป็นกลุ่มข้อมูลทั่วไป

คำว่า "ข้อมูลจำนวนมาก" เกี่ยวข้องกับ "ข้อมูลจำนวนมาก" ดังนั้นจึงเป็นเรื่องปกติที่จะใช้ข้อมูลดิบดั้งเดิมโดยไม่จำเป็นต้องแปลงเป็น SQL ไฟล์ข้อมูลดิบทั่วไปสำหรับ "การแทรกจำนวนมาก" เป็นรูปแบบCSVและJSON

เม็ดมีดที่มีการแปลงบางส่วน

ในแอปพลิเคชันETLและกระบวนการกลืนกินเราจำเป็นต้องเปลี่ยนข้อมูลก่อนที่จะแทรก ตารางชั่วคราวใช้พื้นที่ดิสก์ (มาก) และไม่ใช่วิธีที่เร็วกว่าในการทำ PostgreSQL ต่างประเทศข้อมูลเสื้อคลุม (FDW) เป็นตัวเลือกที่ดีที่สุด

ตัวอย่างเช่น CSV สมมติว่าtablename (x, y, z)บน SQL และไฟล์ CSV เช่น

fieldname1,fieldname2,fieldname3
etc,etc,etc
... million lines ...

คุณสามารถใช้คลาสสิก SQL COPYเพื่อโหลด ( ตามที่เป็นข้อมูลดั้งเดิม) ลงไปtmp_tablename, พวกเขาแทรกข้อมูลที่กรองเข้าไปในtablename... แต่เพื่อหลีกเลี่ยงการใช้ดิสก์ที่ดีที่สุดคือการติดเครื่องโดยตรงโดย

INSERT INTO tablename (x, y, z)
  SELECT f1(fieldname1), f2(fieldname2), f3(fieldname3) -- the transforms 
  FROM tmp_tablename_fdw
  -- WHERE condictions
;

คุณต้องเตรียมฐานข้อมูลสำหรับ FDW และtmp_tablename_fdwคุณสามารถใช้ฟังก์ชั่นที่สร้างมันขึ้นมาแทน :

CREATE EXTENSION file_fdw;
CREATE SERVER import FOREIGN DATA WRAPPER file_fdw;
CREATE FOREIGN TABLE tmp_tablename_fdw(
  ...
) SERVER import OPTIONS ( filename '/tmp/pg_io/file.csv', format 'csv');

ตัวอย่างเช่น JSON ชุดของไฟล์สองไฟล์myRawData1.jsonและRanger_Policies2.jsonสามารถติดตั้งโดย:

INSERT INTO tablename (fname, metadata, content)
 SELECT fname, meta, j  -- do any data transformation here
 FROM jsonb_read_files('myRawData%.json')
 -- WHERE any_condiction_here
;

โดยที่ฟังก์ชันjsonb_read_files ()อ่านไฟล์ทั้งหมดของโฟลเดอร์ที่กำหนดโดย mask:

CREATE or replace FUNCTION jsonb_read_files(
  p_flike text, p_fpath text DEFAULT '/tmp/pg_io/'
) RETURNS TABLE (fid int,  fname text, fmeta jsonb, j jsonb) AS $f$
  WITH t AS (
     SELECT (row_number() OVER ())::int id, 
           f as fname,
           p_fpath ||'/'|| f as f
     FROM pg_ls_dir(p_fpath) t(f)
     WHERE    f like p_flike
  ) SELECT id,  fname,
         to_jsonb( pg_stat_file(f) ) || jsonb_build_object('fpath',p_fpath),
         pg_read_file(f)::jsonb
    FROM t
$f$  LANGUAGE SQL IMMUTABLE;

ไม่มีสตรีม gzip

วิธีที่บ่อยที่สุดสำหรับ "การนำเข้าไฟล์" (ฉีดในบิ๊กดาต้า) กำลังรักษาไฟล์ต้นฉบับใน รูปแบบ gzipและถ่ายโอนด้วยอัลกอริธึมการสตรีมสิ่งใดก็ตามที่สามารถทำงานได้อย่างรวดเร็วและไม่มีการใช้ดิสก์ในท่อยูนิกซ์:

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

ดังนั้นในอุดมคติ (ในอนาคต) เป็นเซิร์ฟเวอร์ตัวเลือก.csv.gzสำหรับรูปแบบ

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