Cách nhanh nhất để chèn số lượng lớn vào Postgres là gì?


241

Tôi cần lập trình chèn 10 triệu bản ghi vào cơ sở dữ liệu postgres. Hiện tại tôi đang thực thi 1000 câu lệnh chèn trong một "truy vấn" duy nhất.

Có cách nào tốt hơn để làm điều này không, một số câu lệnh chèn số lượng lớn mà tôi không biết?

Câu trả lời:


211

PostgreSQL có một hướng dẫn về cách tạo cơ sở dữ liệu tốt nhất ban đầu và họ đề nghị sử dụng lệnh COPY cho các hàng tải hàng loạt. Hướng dẫn có một số mẹo hay khác về cách tăng tốc quá trình, như xóa chỉ mục và khóa ngoại trước khi tải dữ liệu (và thêm lại chúng sau đó).


33
Tôi đã viết chi tiết hơn một chút để giải thích chi tiết trong stackoverflow.com/questions/12206600/ .
Craig Ringer

24
@CraigRinger Wow, "chi tiết hơn một chút" là cách nói hay nhất mà tôi đã thấy cả tuần;)
thúc vào

Hãy dùng thử NpgsqlBulkCopy Cài đặt-Gói
Elyor

1
Các chỉ mục -Since cũng được sử dụng để bố trí vật lý các bản ghi db. Không chắc chắn nếu loại bỏ các chỉ mục trong bất kỳ cơ sở dữ liệu là một ý tưởng tốt.
Farjad

Nhưng đề nghị của bạn, không có gì trong Bộ nhớ !!! Và nếu kích thước lô của bạn có thể là số nhỏ, thì nó rất kém hoạt động, đó là lớp :( Tôi thử lớp npgsql CopyIn, vì nó giống như ánh xạ định dạng CSV trong câu lệnh truy vấn PG. Bạn có thể thử Bảng lớn không?
Elyor

93

Có một cách khác để sử dụng COPY, đó là cú pháp giá trị đa hướng mà Postgres hỗ trợ. Từ tài liệu :

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

Đoạn mã trên chèn hai hàng, nhưng bạn có thể mở rộng tùy ý, cho đến khi bạn đạt được số lượng mã thông báo đã chuẩn bị tối đa (có thể là $ 999, nhưng tôi không chắc chắn 100% về điều đó). Đôi khi người ta không thể sử dụng COPY và đây là sự thay thế xứng đáng cho những tình huống đó.


12
Bạn có biết hiệu suất của phương pháp này so với COPY như thế nào không?
Cấp Humphries

Nếu bạn gặp phải vấn đề về quyền, trước khi thử điều này, hãy sử dụng SAO CHÉP ... TỪ STDIN
Andrew Scott Evans

Nếu bạn đang sử dụng bảo mật cấp hàng, đây là cách tốt nhất bạn có thể làm. "SAO CHÉP TỪ không được hỗ trợ cho các bảng có bảo mật cấp hàng" kể từ phiên bản 12.
Eloff

COPY nhanh hơn rất nhiều so với INSERT mở rộng
hipertracker

24

Một cách để tăng tốc mọi thứ là thực hiện rõ ràng nhiều lần chèn hoặc sao chép trong một giao dịch (giả sử 1000). Hành vi mặc định của Postgres là cam kết sau mỗi câu lệnh, vì vậy bằng cách gộp các cam kết, bạn có thể tránh được một số chi phí. Như hướng dẫn trong câu trả lời của Daniel nói, bạn có thể phải tắt chế độ tự động để điều này hoạt động. Cũng lưu ý nhận xét ở phía dưới cho thấy việc tăng kích thước của wal_buffers lên 16 MB cũng có thể giúp ích.


1
Điều đáng nói là giới hạn số lượng chèn / bản sao bạn có thể thêm vào cùng một giao dịch có thể cao hơn nhiều so với bất kỳ thứ gì bạn sẽ thử. Bạn có thể thêm hàng triệu và hàng triệu hàng trong cùng một giao dịch và không gặp vấn đề.
Sumeet Jain

@SumeetJain Có, tôi chỉ nhận xét về tốc độ 'điểm ngọt' về số lượng bản sao / chèn trên mỗi giao dịch.
Dana the Sane

Điều này sẽ khóa bảng trong khi giao dịch đang chạy?
Tiên nữ Lambda

15

UNNESTchức năng với các mảng có thể được sử dụng cùng với cú pháp nhiều giá trị. Tôi nghĩ rằng phương pháp này chậm hơn so với sử dụng COPYnhưng nó hữu ích với tôi khi làm việc với psycopg và python (python listđược truyền để cursor.executetrở thành pg ARRAY):

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

không VALUESsử dụng subselect với kiểm tra tồn tại bổ sung:

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
);

cú pháp tương tự để cập nhật hàng loạt:

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;


9

Nó chủ yếu phụ thuộc vào hoạt động (khác) trong cơ sở dữ liệu. Các hoạt động như thế này có hiệu quả đóng băng toàn bộ cơ sở dữ liệu cho các phiên khác. Một xem xét khác là datamodel và sự hiện diện của các ràng buộc, kích hoạt, vv

Cách tiếp cận đầu tiên của tôi luôn là: tạo bảng (temp) có cấu trúc tương tự bảng mục tiêu (tạo bảng tmp NHƯ chọn * từ mục tiêu trong đó 1 = 0) và bắt đầu bằng cách đọc tệp vào bảng tạm thời. Sau đó, tôi kiểm tra những gì có thể được kiểm tra: trùng lặp, khóa đã tồn tại trong mục tiêu, v.v.

Sau đó, tôi chỉ cần thực hiện "làm chèn vào mục tiêu chọn * từ tmp" hoặc tương tự.

Nếu điều này không thành công hoặc mất quá nhiều thời gian, tôi hủy bỏ nó và xem xét các phương pháp khác (tạm thời bỏ chỉ mục / ràng buộc, v.v.)



6

Tôi vừa gặp phải vấn đề này và muốn giới thiệu csvsql ( bản phát hành ) cho nhập hàng loạt vào Postgres. Để thực hiện chèn hàng loạt, bạn chỉ cần createdbsử dụng csvsql, sau đó kết nối với cơ sở dữ liệu của bạn và tạo các bảng riêng lẻ cho toàn bộ thư mục CSV.

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

1
Đối với csvsql, để xóa sạch csv nguồn khỏi mọi lỗi hình thành có thể xảy ra, tốt nhất bạn nên làm theo các hướng dẫn này , xem thêm tài liệu tại đây
sal

0

Tệp bên ngoài là dữ liệu hàng loạt tốt nhất và điển hình

Thuật ngữ "dữ liệu số lượng lớn" có liên quan đến "rất nhiều dữ liệu", do đó, việc sử dụng dữ liệu thô ban đầu là điều tự nhiên , không cần phải chuyển đổi thành SQL. Các tệp dữ liệu thô điển hình cho "chèn số lượng lớn" là các định dạng CSVJSON .

Chèn số lượng lớn với một số chuyển đổi

Trong các ứng dụng ETL và quy trình nhập dữ liệu, chúng ta cần thay đổi dữ liệu trước khi chèn nó. Bảng tạm thời tiêu tốn (rất nhiều) dung lượng đĩa và đó không phải là cách nhanh hơn để làm điều đó. Trình bao bọc dữ liệu nước ngoài PostgreSQL (FDW) là lựa chọn tốt nhất.

Ví dụ CSV . Giả sử tablename (x, y, z)trên SQL và một tệp CSV như

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

Bạn có thể sử dụng SQL cổ điển COPYđể tải ( như dữ liệu gốc) tmp_tablename, họ chèn dữ liệu đã lọc vào tablename... Nhưng, để tránh tiêu thụ đĩa, cách tốt nhất là nhập trực tiếp vào

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

Bạn cần chuẩn bị cơ sở dữ liệu cho FDW và thay vào đó, tmp_tablename_fdwbạn có thể sử dụng hàm tạo ra nó :

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');

Ví dụ JSON . Một bộ gồm hai tệp myRawData1.jsonRanger_Policies2.jsoncó thể được nhập bằng cách:

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
;

trong đó hàm jsonb_read_files () đọc tất cả các tệp của thư mục, được xác định bởi mặt nạ:

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;

Thiếu luồng gzip

Phương pháp phổ biến nhất cho "nhập tệp" (chủ yếu trong Dữ liệu lớn) là bảo toàn tệp gốc ở định dạng gzip và chuyển nó bằng thuật toán phát trực tuyến , bất cứ thứ gì có thể chạy nhanh và không tiêu thụ đĩa trong các ống unix:

 gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 

Vì vậy, lý tưởng (tương lai) là một tùy chọn máy chủ cho định dạng .csv.gz.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.