如何加快PostgreSQL中的插入性能

Luk*_*101 189 sql postgresql bulkinsert sql-insert

我正在测试Postgres插入性能.我有一个表,其中一列以数字作为数据类型.它上面也有一个索引.我使用此查询填充数据库:

insert into aNumber (id) values (564),(43536),(34560) ...
Run Code Online (Sandbox Code Playgroud)

我通过上面的查询一次非常快速地插入了400万行.数据库达到600万行后,性能每15分钟急剧下降到100万行.有没有提高插入性能的技巧?我需要在这个项目上获得最佳插入性能.

在具有5 GB RAM的计算机上使用Windows 7 Pro.

Cra*_*ger 450

请参阅PostgreSQL手册中的数据库填充数据,这关于该主题的非常常见的文章,以及这个问题.

(请注意,这个答案是关于将数据批量加载到现有数据库中或创建新数据.如果您对数据库恢复性能pg_restore或输出psql执行感兴趣pg_dump,那么大部分内容都不适用,因为pg_dumppg_restore已经完成了完成模式+数据恢复后的触发器和索引).

还有很多工作要做.理想的解决方案是导入UNLOGGED没有索引的表,然后将其更改为记录并添加索引.不幸的是,在PostgreSQL 9.4中,不支持将表更改UNLOGGED为已记录.9.5添加ALTER TABLE ... SET LOGGED允许您这样做.

如果您可以使数据库脱机以进行批量导入,请使用pg_bulkload.

除此以外:

  • 禁用表上的任何触发器

  • 在开始导入之前删除索引,然后重新创建它们.(在一次传递中构建索引所需时间比逐步向其添加相同数据所花费的时间得多,并且得到的索引要紧凑得多).

  • 如果在单个事务中进行导入,则可以安全地删除外键约束,执行导入,并在提交之前重新创建约束.如果导入被分割为多个事务,则不要这样做,因为您可能会引入无效数据.

  • 如果可能,请使用COPY而不是INSERTs

  • 如果你不能使用COPY考虑使用多值INSERTs,如果可行的话.你似乎已经这样做了.不要试图在一个单独列出太多的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 数量 - 它们不太有益,但仍然可以是一个很大的帮助.除非您不关心保留数据,否则请勿在没有正确电源故障保护的情况下使用廉价的SSD.

  • 如果您使用RAID 5或RAID 6进行直接附加存储,请立即停止.备份数据,将RAID阵列重组为RAID 10,然后重试.RAID 5/6对于批量写入性能毫无希望 - 尽管具有大缓存的优秀RAID控制器可以提供帮助.

  • 如果您可以选择使用具有大电池支持的回写高速缓存的硬件RAID控制器,则可以真正提高具有大量提交的工作负载的写入性能.如果您使用commit_delay进行异步提交,或者在批量加载期间执行较少的大事务,则无效.

  • 如果可能,将WAL(pg_xlog)存储在单独的磁盘/磁盘阵列上.在同一磁盘上使用单独的文件系统没什么意义.人们经常选择使用RAID1对来进行WAL.同样,这对具有高提交率的系统有更大的影响,如果您使用未记录的表作为数据加载目标,它几乎没有影响.

您可能也对Optimize PostgreSQL感兴趣以进行快速测试.

  • 任何人都发现使用“ UNLOGGED”可以显着提高速度吗?快速测试显示出10-20%的改善。 (2认同)

Mik*_*e T 14

使用COPY table TO ... WITH BINARY它根据文档" 比文字和CSV格式有所加快." 如果您要插入数百万行,并且您对二进制数据感到满意,则只能执行此操作.

这是Python中的一个示例配方,使用psycopg2和二进制输入.

  • 二进制模式可以节省一些输入的大量时间,例如时间戳,解析它们是非常重要的。对于许多数据类型,它没有提供太多好处,或者由于增加的带宽(例如小整数)甚至可能稍微慢一些。提高它的好点子。 (3认同)

Max*_*kin 11

除了优秀的Craig Ringer的帖子和depesz的博客文章之外,如果你想通过在事务中使用准备语句插入来加速通过ODBC(psqlodbc)接口的插入,那么你需要做一些额外的事情来实现它.工作快:

  1. 通过Protocol=-1在连接字符串中指定,将错误回滚级别设置为"Transaction" .默认情况下,psqlodbc使用"Statement"级别,该级别为每个语句而不是整个事务创建SAVEPOINT,从而使插入更慢.
  2. 通过UseServerSidePrepare=1在连接字符串中指定来使用服务器端预准备语句.如果没有此选项,客户端将发送整个insert语句以及要插入的每一行.
  3. 使用禁用每个语句的自动提交 SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
  4. 插入所有行后,使用提交事务SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);.无需明确打开事务.

不幸的是,psqlodbc SQLBulkOperations通过发出一系列毫无准备的插入语句来"实现" ,因此为了实现最快的插入,需要手动编写上述步骤.


Den*_*nis 8

今天,我在同一问题上花费了大约6个小时。插入以“常规”速度(每100K少于3秒)直到达到5MI(总共30MI)行,然后性能急剧下降(一路下降到每100K 1分钟)。

我不会列出所有无效的内容,并直接切成薄片。

将主键放在目标表(这是一个GUID)上,并且我的30MI或行以每100K小于3秒的恒定速度愉快地流到它们的目的地。


小智 8

如果您碰巧插入带有 UUID 的列(这不完全是您的情况)并添加到 @Dennis答案(我还不能评论),建议不要使用 gen_random_uuid()(需要 PG 9.4 和 pgcrypto 模块)是(a很多)比 uuid_generate_v4() 快

=# explain analyze select uuid_generate_v4(),* from generate_series(1,10000);
                                                        QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=11.674..10304.959 rows=10000 loops=1)
 Planning time: 0.157 ms
 Execution time: 13353.098 ms
(3 filas)
Run Code Online (Sandbox Code Playgroud)

对比


=# explain analyze select gen_random_uuid(),* from generate_series(1,10000);
                                                        QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------
 Function Scan on generate_series  (cost=0.00..12.50 rows=1000 width=4) (actual time=252.274..418.137 rows=10000 loops=1)
 Planning time: 0.064 ms
 Execution time: 503.818 ms
(3 filas)
Run Code Online (Sandbox Code Playgroud)

此外,这是建议的官方方式

笔记

如果您只需要随机生成的(版本 4)UUID,请考虑使用 pgcrypto 模块中的 gen_random_uuid() 函数。

这将 370 万行的插入时间从约 2 小时减少到约 10 分钟。