我正在测试Postgres插入性能。我有一个表,其中一列以数字作为数据类型。它上面也有一个索引。我使用此查询填充数据库:
insert into aNumber (id) values (564),(43536),(34560) ...
我通过上面的查询一次非常快速地插入400万行。数据库达到600万行后,性能每15分钟急剧下降到100万行。有没有提高插入性能的技巧?我需要在这个项目中获得最佳的插入性能。
在具有5 GB RAM的计算机上使用Windows 7 Pro。
答案 0 :(得分:424)
请参阅PostgreSQL手册中的populate a database,有关该主题的depesz's excellent-as-usual article以及this SO question。
(请注意,这个答案是关于将数据批量加载到现有数据库中或创建新数据。如果您对pg_restore
或psql
执行的数据库恢复性能感兴趣pg_dump
输出,其中大部分都不适用,因为pg_dump
和pg_restore
已完成诸如在完成模式+数据恢复后创建触发器和索引之类的事情。
还有很多工作要做。理想的解决方案是导入没有索引的UNLOGGED
表,然后将其更改为记录并添加索引。不幸的是,在PostgreSQL 9.4中,不支持将表从UNLOGGED
更改为已记录。 9.5添加ALTER TABLE ... SET LOGGED
以允许您执行此操作。
如果您可以使数据库脱机以进行批量导入,请使用pg_bulkload
。
否则:
禁用表格上的任何触发器
在开始导入之前删除索引,然后重新创建它们。 (在一次传递中构建索引所花费的时间比在逐步添加相同数据所花费的时间少得多
如果在单个事务中进行导入,则可以安全地删除外键约束,执行导入,并在提交之前重新创建约束。如果导入被分割为多个事务,则不要这样做,因为您可能会引入无效数据。
如果可能,请使用COPY
代替INSERT
s
如果您不能使用COPY
,请考虑使用多值INSERT
,如果可行的话。你似乎已经这样做了。不要尝试在单个VALUES
中列出太多多个值;这些值必须在内存中放置几次,所以每个语句保持几百个。
将您的插入批处理为显式事务,每个事务执行数十万或数百万次插入。 AFAIK没有实际限制,但是通过标记输入数据中每个批次的开头,批处理可以让您从错误中恢复。再一次,你似乎已经这样做了。
使用synchronous_commit=off
和巨大的commit_delay
来降低fsync()费用。但是,如果你将你的工作分成大型交易,这将无济于事。
INSERT
或COPY
并行。多少取决于硬件的磁盘子系统;根据经验,如果使用直连存储,则每个物理硬盘驱动器需要一个连接。
设置较高的checkpoint_segments
值并启用log_checkpoints
。查看PostgreSQL日志并确保它不会过于频繁地检查检查点。
当且仅当您不介意在导入期间系统崩溃时,如果您不介意将整个PostgreSQL集群(您的数据库和同一集群上的任何其他集群)丢失为灾难性损坏,您可以停止Pg,设置{{ 1}},启动Pg,执行导入,然后(重要)停止Pg并再次设置fsync=off
。见WAL configuration。 如果您在PostgreSQL安装的任何数据库中已经存在任何数据,请不要这样做。如果设置fsync=on
,您也可以设置fsync=off
;再次,请记住在导入后重新打开它以防止数据库损坏和数据丢失。请参阅Pg手册中的non-durable settings。
您还应该考虑调整系统:
尽可能使用质量好的 SSD进行存储。具有可靠,受电源保护的回写高速缓存的良好SSD使得提交速度极快。当你遵循上面的建议 - 它减少了磁盘刷新/ full_page_writes=off
的数量 - 它们不那么有用 - 但仍然可以提供很大的帮助。如果没有适当的电源故障保护,请不要使用廉价的SSD,除非您不关心保留数据。
如果您使用RAID 5或RAID 6进行直接附加存储,请立即停止。备份数据,将RAID阵列重组为RAID 10,然后重试。 RAID 5/6对批量写入性能没有希望 - 尽管具有大缓存的优秀RAID控制器可以提供帮助。
如果您可以选择使用具有大电池备份回写缓存的硬件RAID控制器,那么这可以真正提高具有大量提交的工作负载的写入性能。如果您使用commit_delay进行异步提交,或者在批量加载期间执行较少的大事务,则无效。
如果可能,将WAL(fsync()
)存储在单独的磁盘/磁盘阵列上。在同一磁盘上使用单独的文件系统没什么意义。人们经常选择使用RAID1对来进行WAL。同样,这对具有高提交率的系统有更大的影响,如果您使用未记录的表作为数据加载目标,它几乎没有效果。
您可能也对Optimise PostgreSQL for fast testing感兴趣。
答案 1 :(得分:11)
根据文档使用COPY table TO ... WITH BINARY
是“somewhat faster than the text and CSV formats”。只有在要插入数百万行时才能执行此操作,并且如果您对二进制数据感到满意。
这是example recipe in Python, using psycopg2 with binary input。
答案 2 :(得分:10)
除了优秀的Craig Ringer的帖子和depesz的博客文章之外,如果你想通过使用预制的语句插入来加速通过ODBC(psqlodbc)接口的插入交易,你需要做一些额外的事情才能让它快速运作:
Protocol=-1
。默认情况下,psqlodbc使用" Statement" level,为每个语句而不是整个事务创建SAVEPOINT,使插入更慢。UseServerSidePrepare=1
来使用服务器端预准备语句。如果没有此选项,客户端将发送整个insert语句以及每个插入的行。SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);
提交事务。无需明确打开交易。不幸的是,psqlodbc&#34;实现&#34; SQLBulkOperations
通过发布一系列无准备的插入语句,以便实现最快的插入,需要手动编写上述步骤。
答案 3 :(得分:6)
我今天在同一问题上花费了大约6个小时。插入以“常规”速度(每100K小于3秒)直到达到5MI(总共30MI)行,然后性能急剧下降(一直下降到每100K 1分钟)。
我不会列出所有无效的内容,并直接切成肉。
我将主键放到了目标表(这是一个GUID)上,我的30MI或行愉快地以不到每100K 3秒的恒定速度流向目的地。
答案 4 :(得分:3)
如果您碰巧要插入带有UUID的列(并非完全是您的情况)并添加到@Dennis answer(我还不能发表评论),建议不要使用gen_random_uuid()(需要PG 9.4和pgcrypto模块)比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)
vs
=# 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)
此外,这是suggested的官方方式
注意
如果只需要随机生成的(版本4)UUID,请考虑使用pgcrypto模块中的gen_random_uuid()函数。
对于370万行,这将插入时间从大约2小时缩短为大约10分钟。
答案 5 :(得分:1)
为获得最佳插入性能,请禁用索引(如果这是您的选项)。除此之外,更好的硬件(磁盘,内存)也很有帮助
答案 6 :(得分:-1)
我也遇到了这个插入性能问题。我的解决方案是产生一些例程来完成插入工作。在此期间,having min(role_id) = 2 and min(role_id) = max(role_id) and count(role_id) = count(*)
应该被赋予适当的数字,否则会提醒太多的开放连接错误。
SetMaxOpenConns
我的项目的加载速度要快得多。这段代码只是简单介绍了它是如何工作的。读者应该能够轻松修改它。