如何提高 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
,它也可以是true
和false
。
当然,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 的 DROP/CREATE/INSERT 语句中简洁地使用 SET 变量?
如何将 DELETE 的返回值插入到 postgresql 中的 INSERT 中?