如何加快PostgreSQL中的插入性能
我正在测试Postgres插入性能。 我有一个表格,其中一列的数字作为其数据类型。 还有一个索引。 我用这个查询填充数据库:
insert into aNumber (id) values (564),(43536),(34560) ...
我用上面的查询一次很快地插入了4百万行。 数据库达到600万行之后,性能每15分钟急剧下降到100万行。 是否有任何技巧来提高插入性能? 我需要这个项目的最佳插入性能。
在具有5 GB RAM的计算机上使用Windows 7 Pro。
请参阅在PostgreSQL手册中填充数据库 , depesz关于该主题的一如既往的文章 ,以及这个问题 。
(请注意,这个答案是关于将数据批量加载到现有数据库或创建一个新数据的情况。如果您对使用pg_restore
或psql
执行pg_dump
输出感兴趣的数据库恢复性能,大部分内容不适用,因为pg_dump
和pg_restore
已经完成了在完成模式+数据恢复之后创建触发器和索引的事情) 。
有很多事情要做。 理想的解决方案是导入到没有索引的UNLOGGED
表中,然后将其更改为记录并添加索引。 不幸的是,在PostgreSQL 9.4中,不支持将表从UNLOGGED
更改为记录。 9.5添加了ALTER TABLE ... SET LOGGED
来允许你这样做。
如果您可以将数据库脱机以进行批量导入,请使用pg_bulkload
。
除此以外:
-
禁用表上的任何触发器
-
在开始导入之前删除索引,之后重新创建它们。 (一次构建索引需要的时间比逐步增加相同的数据要少得多,而且索引结构更紧凑)。
-
如果在单个事务中进行导入,那么放弃外键约束,执行导入并在提交之前重新创建约束是安全的。 如果导入分散在多个事务中,请不要这样做,因为您可能会引入无效的数据。
-
如果可能,请使用
COPY
而不是INSERT
-
如果您不能使用
COPY
考虑使用多值INSERT
(如果可行)。 你似乎已经这样做了。 不要试图在一个VALUES
列出太多的值。 这些值必须适合内存几次,所以每个语句保持在几百个。 -
批量插入显式事务,每个事务执行数十万或数百万的插入。 AFAIK没有实际的限制,但通过在输入数据中标记每个批次的开始,批处理可以让您从错误中恢复。 再一次,你似乎已经这样做了。
-
使用
synchronous_commit=off
和一个巨大的commit_delay
来降低fsync()成本。 尽管如此,如果你把你的工作投入到大交易中,这并没有什么帮助。 -
从多个连接并行
INSERT
或COPY
。 多少取决于您的硬件的磁盘子系统; 作为一个经验法则,如果使用直接附加存储,则需要每个物理硬盘一个连接。 -
设置一个高
checkpoint_segments
值并启用log_checkpoints
。 查看PostgreSQL日志,确保它不会抱怨过于频繁的检查点。 -
当且仅当您不介意在导入过程中系统崩溃时将整个PostgreSQL集群(您的数据库和其他集群中的其他数据库)丢到灾难性的损坏状态,您可以停止Pg,设置
fsync=off
,启动Pg,你的导入,然后(非常)停止Pg并且再次设置fsync=on
。 请参阅WAL配置 。 如果在PostgreSQL安装的任何数据库中已经有关心的数据,请不要这样做。 如果你设置fsync=off
你也可以设置full_page_writes=off
; 再次,只要记住在导入后重新打开它,以防止数据库损坏和数据丢失。 请参阅Pg手册中的非耐用设置 。
你也应该看看调整你的系统:
-
尽可能使用高质量的 SSD进行存储。 具有可靠的,电源保护的回写缓存的优秀SSD使得提交速度非常快。 当你按照上面的建议(这会减少磁盘刷新/
fsync()
s的数量fsync()
的时候,它们的好处不大,但是仍然是一个很大的帮助。 除非您不关心保存数据,否则不要使用廉价的固态硬盘。 -
如果您将RAID 5或RAID 6用于直连存储,请立即停止。 将您的数据备份,将RAID阵列重新组合为RAID 10,然后重试。 RAID 5/6对于批量写入性能是无望的 – 尽管具有大缓存的好RAID控制器可以提供帮助。
-
如果您可以选择使用带有大容量电池供电的写回缓存的硬件RAID控制器,那么可以提高写入性能,从而为具有大量提交的工作负载提供更高的写入性能。 如果您使用commit_delay进行异步提交,或者在批量加载期间执行较少的大事务,那么这并没有多大帮助。
-
如果可能的话,将WAL(
pg_xlog
)存储在单独的磁盘/磁盘阵列上。 在同一个磁盘上使用单独的文件系统没什么意义。 人们通常选择使用WAL的RAID1对。 同样,这对于提交率较高的系统有更多的影响,如果您使用不记录的表作为数据加载目标,则影响不大。
您可能也有兴趣优化PostgreSQL进行快速测试 。
根据文档使用COPY table TO ... WITH BINARY
是“ 比文本和CSV格式稍快 ”。 只有在有数百万行插入的情况下才能做到这一点,而且如果您对二进制数据感到满意的话。
这是Python中的一个例子,使用psycopg2和二进制输入 。
除了优秀的Craig Ringer的文章和depesz的博客文章之外,如果您想通过在事务内部使用prepared-statement插入来加速通过ODBC( psqlodbc )接口的插入,还需要做一些额外的事情来完成快速工作:
- 通过在连接字符串中指定
Protocol=-1
,将错误回滚级别设置为“Transaction”。 默认情况下,psqlodbc使用“语句”级别,它为每个语句而不是整个事务创建一个SAVEPOINT,从而使插入变慢。 - 通过在连接字符串中指定
UseServerSidePrepare=1
来使用服务器端预处理语句。 没有这个选项,客户端会发送整个插入语句以及插入的每一行。 - 使用
SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
禁用每个语句上的自动提交SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
- 一旦所有行都被插入,使用
SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);
提交事务SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);
。 没有必要明确地打开一个交易。
不幸的是,psqlodbc通过发布一系列准备SQLBulkOperations
插入语句来“实现” SQLBulkOperations
,所以为了实现最快的插入,需要手动对上述步骤进行编码。
为了获得最佳的插入性能,如果这是您的选项,请禁用索引。 除此之外,更好的硬件(磁盘,内存)也是有帮助的