如何提高 PostgreSQL 在 INSERT 上的性能?

Posted

技术标签:

【中文标题】如何提高 PostgreSQL 在 INSERT 上的性能?【英文标题】:How to improve PostgreSQL performance on INSERT? 【发布时间】:2017-04-25 18:24:09 【问题描述】:

我编写了一个 Node.js 应用程序,它将大量记录写入 PostgreSQL 9.6 数据库。不幸的是,它感觉很慢。为了能够测试事物,我创建了一个重现场景的short but complete 程序:

'use strict';

const async = require('async'),
      pg = require('pg'),
      uuid = require('uuidv4');

const pool = new pg.Pool(
  protocol: 'pg',
  user: 'golo',
  host: 'localhost',
  port: 5432,
  database: 'golo'
);

const records = [];

for (let i = 0; i < 10000; i++) 
  records.push( id: uuid(), revision: i, data:  foo: 'bar', bar: 'baz' , flag: true );


pool.connect((err, database, close) => 
  if (err) 
    /* eslint-disable no-console */
    return console.log(err);
    /* eslint-enable no-console */
  

  database.query(`
    CREATE TABLE IF NOT EXISTS "foo" (
      "position" bigserial NOT NULL,
      "id" uuid NOT NULL,
      "revision" integer NOT NULL,
      "data" jsonb NOT NULL,
      "flag" boolean NOT NULL,

      CONSTRAINT "foo_pk" PRIMARY KEY("position"),
      CONSTRAINT "foo_index_id_revision" UNIQUE ("id", "revision")
    );
  `, errQuery => 
    if (errQuery) 
      /* eslint-disable no-console */
      return console.log(errQuery);
      /* eslint-enable no-console */
    

    async.series(
      beginTransaction (done) 
        /* eslint-disable no-console */
        console.time('foo');
        /* eslint-enable no-console */
        database.query('BEGIN', done);
      ,
      saveRecords (done) 
        async.eachSeries(records, (record, doneEach) => 
          database.query(
            name: 'save',
            text: `
              INSERT INTO "foo"
                ("id", "revision", "data", "flag")
              VALUES
                ($1, $2, $3, $4) RETURNING position;
            `,
            values: [ record.id, record.revision, record.data, record.flag ]
          , (errQuery2, result) => 
            if (errQuery2) 
              return doneEach(errQuery2);
            

            record.position = Number(result.rows[0].position);
            doneEach(null);
          );
        , done);
      ,
      commitTransaction (done) 
        database.query('COMMIT', done);
      
    , errSeries => 
      /* eslint-disable no-console */
      console.timeEnd('foo');
      /* eslint-enable no-console */
      if (errSeries) 
        return database.query('ROLLBACK', errRollback => 
          close();

          if (errRollback) 
            /* eslint-disable no-console */
            return console.log(errRollback);
            /* eslint-enable no-console */
          
          /* eslint-disable no-console */
          console.log(errSeries);
          /* eslint-enable no-console */
        );
      

      close();
      /* eslint-disable no-console */
      console.log('Done!');
      /* eslint-enable no-console */
    );
  );
);

插入 10.000 行的性能是 2.5 秒。这还不错,但也不是很好。我可以做些什么来提高速度?

到目前为止我的一些想法:

使用准备好的语句。如您所见,我已经这样做了,这将速度提高了约 30 %。 使用单个INSERT 命令一次插入多行。不幸的是,这是不可能的,因为在现实中,需要写入的记录数量因调用而异,而且参数数量的不同使得无法使用准备好的语句。 使用COPY 而不是INSERT:我不能使用这个,因为这发生在运行时,而不是初始化时。 使用text 而不是jsonb:没有任何改变。 使用json 而不是jsonb:也没有改变任何东西。

关于现实中发生的数据的更多说明:

revision 不一定会增加。这只是一个数字。 flag 并不总是true,它也可以是truefalse。 当然,data 字段也包含不同的数据。

所以最后归结为:

有哪些方法可以显着加快对INSERT 的多次单次调用?

【问题讨论】:

你确定瓶颈不是 Node 和 Postgres 之间的通信时间吗?您是否对直接在 Postgres 上运行的 10 个插入进行了基准测试? 不,我不是。请注意,它是 10k 次插入,而不是 10 次,那么我怎么能直接在 PostgreSQL 中这样做呢? (即使,那么问题是,如何减少这两者之间的通信时间;-)) 2 秒内 10K 插入并没有让我觉得太糟糕。 反正还不够好;-) 如果我将 SQL 放入脚本文件并从 PGAdmin 4 运行它,它会告诉我查询在 1 秒内成功返回。 【参考方案1】:

使用单个 INSERT 命令一次插入多行。不幸的是,这是不可能的,因为在现实中,需要写入的记录数量因调用而异,并且参数数量的不同使得无法使用准备好的语句。

这是正确的答案,后面是无效的反驳。

您可以在循环中生成多行插入,每个查询大约 1000 到 10,000 条记录,具体取决于记录的大小。

而且您根本不需要为此准备好的语句。

请参阅我写的关于相同问题的这篇文章:Performance Boost。

根据这篇文章,我的代码能够在 50 毫秒以下内插入 10,000 条记录。

一个相关问题:Multi-row insert with pg-promise。

【讨论】:

你是对的,谢谢:-)。需要注意的一件事是,您仍然可以将其与(动态创建的)准备好的语句结合使用,从而进一步提高性能。 @GoloRoden 有一些 8K 参数的限制,可以一次在准备好的语句中传递,这限制了它在这种情况下的可用性。 这只影响准备好的语句吗? @GoloRoden 是的。

以上是关于如何提高 PostgreSQL 在 INSERT 上的性能?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 flask_sqlalchemy 中使用 PostgreSQL 的“INSERT...ON CONFLICT”(UPSERT)功能?

PostgreSQL:更新...缺少插入?

如何在 PostgreSQL 的 DROP/CREATE/INSERT 语句中简洁地使用 SET 变量?

如何将 DELETE 的返回值插入到 postgresql 中的 INSERT 中?

PostgreSQL 9.5.4数据库快速INSERT大量数据研究

如何在 PostgreSQL 中进行 UPSERT(合并、插入……重复更新)?