【问题标题】:What's the fastest way to do a bulk insert into Postgres?将批量插入 Postgres 的最快方法是什么?
【发布时间】:2010-10-20 00:37:07
【问题描述】:

我需要以编程方式将数百万条记录插入到 postgres 数据库中。目前,我正在一个“查询”中执行 1000 条插入语句。

有没有更好的方法来做到这一点,一些我不知道的批量插入语句?

【问题讨论】:

    标签: postgresql bulkinsert


    【解决方案1】:

    PostgreSQL 有 a guide 关于如何最好地最初填充数据库,他们建议使用 COPY 命令批量加载行。该指南还有一些其他关于如何加快流程的好技巧,例如在加载数据之前删除索引和外键(然后再将它们添加回来)。

    【讨论】:

    • 我在stackoverflow.com/questions/12206600/…也写了一些更详细的说明。
    • @CraigRinger 哇,“更详细一点”是我整个星期看到的最好的轻描淡写;)
    • 尝试安装包 NpgsqlBulkCopy
    • - 因为索引也用于数据库记录的物理布局。不确定删除任何数据库中的索引是否是个好主意。
    • 但是你推荐的,内存中什么都没有!!!如果你的批量大小可能很小,那么它的类就非常非常糟糕:(我尝试使用 npgsql CopyIn 类,因为它就像 PG 查询语句中的 CSV 格式映射一样。你可以试试 Big Table?
    【解决方案2】:

    还有一种使用 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,这是一种值得替代这些情况的方法。

    【讨论】:

    • 你知道这种方法的性能与COPY相比如何吗?
    • 如果遇到权限问题,在尝试之前,请使用 COPY ... FROM STDIN
    • 如果您使用的是行级安全性,这是您能做的最好的事情。从版本 12 开始,“具有行级安全性的表不支持 COPY FROM”。
    • COPY 比扩展 INSERT 快很多
    • 在这种过程(原始数据摄取)中最重要的是转换,用 SQL 标准表达(不使用外来工具) .见stackoverflow.com/a/62493516/287948
    【解决方案3】:

    加快速度的一种方法是在事务中显式执行多次插入或复制(例如 1000 次)。 Postgres 的默认行为是在每条语句之后提交,因此通过批量提交,可以避免一些开销。正如丹尼尔回答中的指南所说,您可能必须禁用自动提交才能使其正常工作。另请注意底部的注释,建议将 wal_buffers 的大小增加到 16 MB 也可能有所帮助。

    【讨论】:

    • 值得一提的是,您可以向同一事务添加多少插入/复制的限制可能比您尝试的任何操作都高得多。您可以在同一个事务中添加数百万行,而不会遇到问题。
    • @SumeetJain 是的,我只是在评论每笔交易的副本/插入数量方面的速度“最佳位置”。
    • 这会在事务运行时锁定表吗?
    【解决方案4】:

    UNNEST 带有数组的函数可以与多行 VALUES 语法一起使用。我认为这种方法比使用 COPY 慢,但它对我使用 psycopg 和 python 很有用(传递给 cursor.execute 的 python list 变为 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;
    

    【讨论】:

      【解决方案5】:

      您可以使用COPY table TO ... WITH BINARY,即“somewhat faster than the text and CSV formats”。仅当您要插入数百万行并且对二进制数据感到满意时才这样做。

      这是example recipe in Python, using psycopg2 with binary input

      【讨论】:

        【解决方案6】:

        这主要取决于数据库中的(其他)活动。像这样的操作有效地冻结了其他会话的整个数据库。另一个考虑因素是数据模型和约束、触发器等的存在。

        我的第一种方法始终是:创建一个结构类似于目标表的(临时)表(create table tmp AS select * from target where 1=0),然后首先将文件读入临时表。 然后我检查可以检查的内容:重复项、目标中已经存在的键等。

        然后我只需执行“do insert into target select * from tmp”或类似的操作。

        如果失败或耗时过长,我会中止它并考虑其他方法(暂时删除索引/约束等)

        【讨论】:

          【解决方案7】:

          外部文件是最好和典型的批量数据

          “批量数据”一词与“大量数据”有关,因此使用原始原始数据是很自然的,无需将其转换为SQL。 “批量插入”的典型原始数据文件是 CSVJSON 格式。

          进行一些转换的批量插入

          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 准备数据库,而静态 tmp_tablename_fdw 您可以使用 a function that generates it

          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 format 上保存原始文件并使用 streaming algorithm 传输它,任何可以在 unix 管道中快速运行且不消耗磁盘的东西:

           gunzip remote_or_local_file.csv.gz | convert_to_sql | psql 
          

          如此理想(未来)是一个服务器选项,格式为.csv.gz

          【讨论】:

            【解决方案8】:

            我使用本地 libpq 方法实现了非常快速的 Postgresq 数据加载器。 试试我的包https://www.nuget.org/packages/NpgsqlBulkCopy/

            【讨论】:

              【解决方案9】:

              我刚遇到这个问题,建议将 csvsql (releases) 批量导入到 Postgres。要执行批量插入,您只需 createdb,然后使用 csvsql,它会连接到您的数据库并为整个 CSV 文件夹创建单独的表。

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

              【讨论】:

              • 对于 csvsql,为了同时清除源 csv 中任何可能的格式错误,最好关注these instructions,更多文档here
              【解决方案10】:

              可能我已经迟到了。但是,Bytefish 有一个名为 pgbulkinsert 的 Java 库。我和我的团队能够在 15 秒内批量插入 100 万条记录。当然,我们还执行了一些其他操作,比如从 Minio 上的文件中读取 1M+ 记录,在 1M+ 记录的顶部进行几次处理,如果重复则过滤掉记录,最后将 1M 记录插入 Postgres 数据库.所有这些过程都在15秒内完成。我不记得执行数据库操作需要多少时间,但我认为大约不到 5 秒。从https://www.bytefish.de/blog/pgbulkinsert_bulkprocessor.html了解更多详情

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 2010-09-06
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多