我需要以编程方式将10百万条记录插入到postgres数据库中。目前我在一个“查询”中执行1000个插入语句。
有没有更好的方法来做到这一点,我不知道一些批量插入语句?
答案 0 :(得分:180)
答案 1 :(得分:69)
还有一种使用COPY的替代方法,它是Postgres支持的多行值语法。来自documentation:
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,这是对这些情况的有效替代。
答案 2 :(得分:19)
加快速度的一种方法是在事务中明确执行多个插入或复制(比如1000)。 Postgres的默认行为是在每个语句之后提交,因此通过批量提交,可以避免一些开销。正如Daniel的回答所说,您可能必须禁用自动提交才能使用。另请注意底部的注释表明将wal_buffers的大小增加到16 MB也可能有所帮助。
答案 3 :(得分:10)
UNNEST
函数可以与多行VALUES语法一起使用。我认为这个方法比使用COPY
慢,但是在使用psycopg和python时很有用(传递给list
的python cursor.execute
变成了pg {{1} }):
ARRAY
没有INSERT INTO tablename (fieldname1, fieldname2, fieldname3)
VALUES (
UNNEST(ARRAY[1, 2, 3]),
UNNEST(ARRAY[100, 200, 300]),
UNNEST(ARRAY['a', 'b', 'c'])
);
使用带有额外存在检查的subselect:
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
);
答案 4 :(得分:9)
您可以使用“somewhat faster than the text and CSV formats”COPY table TO ... WITH BINARY
。只有在要插入数百万行时才能执行此操作,并且如果您对二进制数据感到满意。
这是example recipe in Python, using psycopg2 with binary input。
答案 5 :(得分:7)
它主要取决于数据库中的(其他)活动。这样的操作有效地冻结了整个数据库以用于其他会话。另一个考虑因素是数据模型以及约束,触发器等的存在。
我的第一种方法是:创建一个(temp)表,其结构类似于目标表(创建表tmp AS select * from target,其中1 = 0),然后将文件读入临时表。 然后我检查可以检查的内容:重复项,目标中已存在的密钥等等。
然后我只是做一个“插入目标select * from tmp”或类似的东西。
如果失败,或者耗时太长,我会中止它并考虑其他方法(暂时删除索引/约束等)
答案 6 :(得分:5)
我使用原生libpq方法实现了非常快速的Postgresql数据加载器。 试试我的包https://www.nuget.org/packages/NpgsqlBulkCopy/
答案 7 :(得分:5)
我刚遇到此问题,并建议csvsql批量导入Postgres。要执行批量插入,您只需createdb
然后使用csvsql
,它连接到您的数据库并为整个CSV文件夹创建单独的表。
$ createdb test
$ csvsql --db postgresql:///test --insert examples/*.csv
答案 8 :(得分:2)
术语“大量数据”与“大量数据”相关,因此很自然地使用原始原始数据,而无需将其转换为SQL。 “批量插入”的典型原始数据文件为CSV和JSON格式。
在ETL应用程序和提取过程中,我们需要在插入数据之前对其进行更改。临时表会占用(大量)磁盘空间,但这并不是更快的方法。 PostgreSQL foreign-data wrapper(FDW)是最佳选择。
CSV示例。假设在SQL上使用tablename (x, y, z)
和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准备数据库,而可以使用a function that generates it来使用静态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()读取由掩码定义的文件夹的所有文件:
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 format上,并通过streaming algorithm进行传输,任何可以快速运行且在unix管道中不会消耗磁盘的东西:
gunzip remote_or_local_file.csv.gz | convert_to_sql | psql
理想的(未来)是格式为.csv.gz
的服务器选项。
答案 9 :(得分:0)
可能我已经迟到了。但是,Bytefish 有一个名为 pgbulkinsert
的 Java 库。我和我的团队能够在 15 秒内批量插入 100 万条记录。当然,我们还执行了一些其他操作,例如,从位于 Minio 上的文件中读取 1M+ 条记录,在 1M+ 条记录的顶部进行一些处理,过滤掉重复的记录,最后将 1M 条记录插入 Postgres 数据库.所有这些过程都在 15 秒内完成。我不记得具体执行数据库操作需要多少时间,但我认为大约不到 5 秒。从 https://www.bytefish.de/blog/pgbulkinsert_bulkprocessor.html