ฉันต้องการแทรกระเบียน 10 ล้านรายการโดยทางโปรแกรมลงในฐานข้อมูล postgres ปัจจุบันฉันกำลังดำเนินการของคำสั่งแทรก 1,000 รายการใน "แบบสอบถาม" เดียว
มีวิธีที่ดีกว่าในการทำคำสั่งแทรกจำนวนมากที่ฉันไม่รู้หรือไม่?
ฉันต้องการแทรกระเบียน 10 ล้านรายการโดยทางโปรแกรมลงในฐานข้อมูล postgres ปัจจุบันฉันกำลังดำเนินการของคำสั่งแทรก 1,000 รายการใน "แบบสอบถาม" เดียว
มีวิธีที่ดีกว่าในการทำคำสั่งแทรกจำนวนมากที่ฉันไม่รู้หรือไม่?
คำตอบ:
PostgreSQL มีคำแนะนำเกี่ยวกับวิธีการเติมฐานข้อมูลที่ดีที่สุดในตอนแรกและพวกเขาแนะนำให้ใช้คำสั่งCOPYสำหรับการโหลดแถวจำนวนมาก คู่มือมีเคล็ดลับที่ดีเกี่ยวกับวิธีเพิ่มความเร็วของกระบวนการเช่นลบดัชนีและคีย์ต่างประเทศก่อนที่จะโหลดข้อมูล (และเพิ่มกลับภายหลัง)
มีทางเลือกอื่นในการใช้ 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 ได้และนี่เป็นการทดแทนที่คุ้มค่าสำหรับสถานการณ์เหล่านั้น
วิธีหนึ่งในการเพิ่มความเร็วคือการแทรกหรือคัดลอกหลายรายการอย่างชัดเจนภายในธุรกรรม (พูด 1,000) พฤติกรรมเริ่มต้นของ Postgres คือการยอมรับหลังจากแต่ละคำสั่งดังนั้นโดยการแบทช์คอมมิทคุณสามารถหลีกเลี่ยงค่าใช้จ่ายบางอย่าง ตามคำแนะนำในคำตอบของ Daniel คุณอาจต้องปิดการใช้งาน autocommit เพื่อให้มันใช้งานได้ นอกจากนี้โปรดทราบความคิดเห็นที่ด้านล่างที่แนะนำการเพิ่มขนาดของ wal_buffers เป็น 16 MB อาจช่วยได้เช่นกัน
UNNEST
ฟังก์ชั่นที่มีอาร์เรย์สามารถใช้พร้อมกับไวยากรณ์หลายค่า ฉันคิดว่าวิธีนี้ช้ากว่าการใช้COPY
แต่มันมีประโยชน์กับฉันในการทำงานกับ psycopg และ python (python list
ถูกส่งไปยังcursor.execute
pg 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;
คุณสามารถใช้COPY table TO ... WITH BINARY
สิ่งที่ " ค่อนข้างเร็วกว่ารูปแบบข้อความและ CSV " ทำเช่นนี้ต่อเมื่อคุณมีจำนวนแถวนับล้านที่จะแทรกและหากคุณสะดวกสบายกับข้อมูลไบนารี
นี่คือสูตรตัวอย่างเช่นในหลามใช้ psycopg2 ด้วยการป้อนข้อมูลไบนารี
ส่วนใหญ่ขึ้นอยู่กับกิจกรรม (อื่น ๆ ) ในฐานข้อมูล การดำเนินการเช่นนี้ทำให้ฐานข้อมูลทั้งหมดของเซสชันอื่นหยุดชะงักได้อย่างมีประสิทธิภาพ สิ่งที่ต้องพิจารณาอีกประการคือโมเดลข้อมูลและการมีอยู่ของข้อ จำกัด ทริกเกอร์ ฯลฯ
วิธีแรกของฉันคือ: สร้างตาราง (temp) ที่มีโครงสร้างคล้ายกับตารางเป้าหมาย (สร้างตาราง tmp AS select * จากเป้าหมายโดยที่ 1 = 0) และเริ่มต้นด้วยการอ่านไฟล์ลงในตาราง temp จากนั้นฉันจะตรวจสอบสิ่งที่สามารถตรวจสอบได้: รายการซ้ำ, คีย์ที่มีอยู่แล้วในเป้าหมาย ฯลฯ
จากนั้นฉันเพิ่งทำ "จะแทรกลงในเป้าหมาย select * จาก tmp" หรือคล้ายกัน
หากสิ่งนี้ล้มเหลวหรือใช้เวลานานเกินไปฉันจะยกเลิกและพิจารณาวิธีอื่น ๆ (วางดัชนี / ข้อ จำกัด ชั่วคราวไว้ชั่วคราว)
ฉันใช้ตัวโหลดข้อมูล Postgresq ที่รวดเร็วมากด้วยวิธี libpq ดั้งเดิม ลองแพ็คเกจของฉันhttps://www.nuget.org/packages/NpgsqlBulkCopy/
ฉันเพิ่งพบปัญหานี้และจะแนะนำcsvsql ( รุ่น ) สำหรับการนำเข้าจำนวนมากไปยัง Postgres เพื่อทำการแทรกจำนวนมากที่คุณต้องการcreatedb
ใช้csvsql
ซึ่งจะเชื่อมต่อกับฐานข้อมูลของคุณและสร้างตารางแยกต่างหากสำหรับทั้งโฟลเดอร์ของ CSV
$ createdb test
$ csvsql --db postgresql:///test --insert examples/*.csv
คำว่า "ข้อมูลจำนวนมาก" เกี่ยวข้องกับ "ข้อมูลจำนวนมาก" ดังนั้นจึงเป็นเรื่องปกติที่จะใช้ข้อมูลดิบดั้งเดิมโดยไม่จำเป็นต้องแปลงเป็น 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และถ่ายโอนด้วยอัลกอริธึมการสตรีมสิ่งใดก็ตามที่สามารถทำงานได้อย่างรวดเร็วและไม่มีการใช้ดิสก์ในท่อยูนิกซ์:
gunzip remote_or_local_file.csv.gz | convert_to_sql | psql
ดังนั้นในอุดมคติ (ในอนาคต) เป็นเซิร์ฟเวอร์ตัวเลือก.csv.gz
สำหรับรูปแบบ