Postgres에 대량 삽입하는 가장 빠른 방법은 무엇입니까?


242

프로그래밍 방식으로 포스트 레코드 데이터베이스에 천만 개의 레코드를 삽입해야합니다. 현재 하나의 "쿼리"에 1000의 insert 문을 실행하고 있습니다.

이 작업을 수행하는 더 좋은 방법이 있습니까, 내가 모르는 대량 삽입 문이 있습니까?

답변:


211

PostgreSQL에는 처음에 데이터베이스를 가장 잘 채우는 방법에 대한 가이드있으며 대량로드 행에 COPY 명령을 사용하는 것이 좋습니다 . 이 가이드에는 데이터를로드하기 전에 인덱스와 외래 키를 제거하고 나중에 다시 추가하는 등 프로세스 속도를 높이는 방법에 대한 유용한 팁이 있습니다.


33
나는 stackoverflow.com/questions/12206600/… 에서 자세히 설명하기 위해 조금 더 자세한 내용을 썼습니다 .
Craig Ringer

24
@CraigRinger 와우, "조금 더 자세한 내용은"내가 일주일 내내 본 최고의 과소 평가;)
culix


1
-인덱스는 db 레코드의 물리적 레이아웃에도 사용됩니다. 데이터베이스에서 인덱스를 제거하는 것이 좋은지 확실하지 않습니다.
Farjad

그러나 당신의 추천, 메모리에 아무것도! 그리고 당신의 배치 크기가 적을 수 있다면, 매우 나쁜 것은 클래스입니다 : (나는 pg 쿼리 문에서 CSV 형식 매핑과 같기 때문에 npgsql CopyIn 클래스를 사용해보십시오. Big Table을 사용해 볼 수 있습니까?
Elyor

94

COPY를 사용하는 대신 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와 어떻게 비교되는지 아십니까?
Grant Humphries

당신이 권한 문제로 실행하면, 전에는 STDIN에서 ...이, 사용의 복사를 시도
앤드류 스캇 에반스

행 수준 보안을 사용하는 경우 이것이 최선의 방법입니다. "행 레벨 보안이있는 테이블에는 COPY FROM이 지원되지 않습니다."버전 12.
Eloff

COPY는 확장 INSERT보다 훨씬 빠릅니다.
hipertracker

24

작업 속도를 높이는 한 가지 방법은 트랜잭션 내에서 여러 삽입 또는 복사를 명시 적으로 수행하는 것입니다 (예 : 1000). Postgres의 기본 동작은 각 명령문 이후에 커밋하는 것이므로 커밋을 일괄 처리하여 약간의 오버 헤드를 피할 수 있습니다. Daniel의 답변 안내서에 나와 있듯이 자동 커밋을 비활성화해야 작동 할 수 있습니다. 또한 wal_buffers의 크기를 16MB로 늘릴 것을 제안하는 주석도 도움이 될 수 있습니다.


1
동일한 트랜잭션에 추가 할 수있는 삽입 / 복사본 수에 대한 제한이 시도하는 것보다 훨씬 높을 수 있습니다. 동일한 트랜잭션 내에 수백만 행과 수백만 행을 추가 할 수 있으며 문제가 발생하지 않습니다.
Sumeet Jain

@SumeetJain 예, 거래 당 복사 / 삽입 수의 관점에서 '스위트 스폿'속도에 주목하고 있습니다.
Dana the Sane

트랜잭션이 실행되는 동안 테이블이 잠깁니까?
Lambda Fairy

15

UNNEST배열이있는 함수는 다중 행 VALUES 구문과 함께 사용할 수 있습니다. 나는이 방법을 사용하는 것보다 느린이라고 생각 해요 COPY하지만 psycopg와 파이썬과 일에 나에게 유용하다 (파이썬 list에 전달 된 cursor.execute페이지가됩니다 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;


9

주로 데이터베이스의 (기타) 활동에 따라 다릅니다. 이와 같은 작업은 다른 세션을 위해 전체 데이터베이스를 효과적으로 고정시킵니다. 다른 고려 사항은 데이터 모델과 제약 조건, 트리거 등의 존재입니다.

첫 번째 방법은 항상 : 목표 테이블과 비슷한 구조로 (임시) 테이블을 만들고 (테이블 = tmp AS select * from target where 1 = 0) 파일을 임시 테이블로 읽는 것부터 시작하십시오. 그런 다음 확인할 수있는 항목을 확인합니다. 중복, 대상에 이미 존재하는 키 등

그런 다음 "대상 선택 * tmp에서 삽입"또는 이와 유사한 작업을 수행합니다.

이것이 실패하거나 너무 오래 걸리면 중단하고 다른 방법을 고려하십시오 (일시적으로 색인 / 제약 조건 삭제 등).



6

방금이 문제가 발생 하여 Postgres로 대량 가져 오기를 위해 csvsql ( releases )을 권장 합니다. 당신은 단순히 것 삽입 일괄 수행하려면 createdb다음 사용 csvsql하여 데이터베이스에 연결하고 CSV를의 전체 폴더에 대한 개별 테이블을 생성합니다.

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

1
csvsql의 경우 가능한 형식 오류로부터 소스 csv도 정리하려면 다음 지침 을 따르는 것이 가장 좋습니다. 여기에
sal

0

외부 파일은 가장 우수하고 일반적인 대량 데이터입니다

"대량 데이터"라는 용어는 "많은 데이터"와 관련이 있으므로 원래 원시 데이터 를 사용하는 것이 자연스럽고 SQL로 변환 할 필요가 없습니다. "대량 삽입"에 대한 일반적인 원시 데이터 파일은 CSVJSON 형식입니다.

약간 변형 된 대량 삽입

에서 ETL의 응용 프로그램 및 섭취 프로세스, 우리는 그것을 삽입하기 전에 데이터를 변경해야합니다. 임시 테이블은 (많은) 디스크 공간을 소비하므로 더 빠른 방법은 아닙니다. PostgreSQL의 해외 데이터 래퍼 (FDW)는 최고의 선택입니다.

CSV 예 . tablename (x, y, z)on 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.jsonRanger_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 () 함수 는 마스크로 정의 된 폴더의 모든 파일을 읽습니다.

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 

이상적인 (미래)는 format에 대한 서버 옵션 입니다 .csv.gz.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.