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

时间:2016-12-10 10:36:13

标签: javascript node.js performance postgresql sql-insert

我编写了一个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的多次单次调用?

1 个答案:

答案 0 :(得分:3)

  

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

这是正确的答案,然后是无效的反驳论据。

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

你根本不需要准备好的陈述。

请参阅此文章,我写了相同的问题:Performance Boost

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

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