如何加快PostgreSQL中的插入性能

问题描述 投票:171回答:6

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

insert into aNumber (id) values (564),(43536),(34560) ...

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

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

sql postgresql bulkinsert sql-insert
6个回答
433
投票

请参阅PostgreSQL手册中的populate a database,关于该主题的depesz's excellent-as-usual articlethis SO question

(请注意,这个答案是关于将数据批量加载到现有数据库中或创建新数据。如果您对pg_restorepsql执行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()成本。但是,如果你将你的工作分成大型交易,这将无济于事。
  • INSERTCOPY从几个连接并行。多少取决于硬件的磁盘子系统;根据经验,如果使用直连存储,则每个物理硬盘驱动器需要一个连接。
  • 设置一个高checkpoint_segments值并启用log_checkpoints。查看PostgreSQL日志并确保它不会抱怨检查点发生得太频繁。
  • 当且仅当您不介意在导入期间系统崩溃时,如果您不介意将整个PostgreSQL集群(您的数据库和同一集群上的任何其他集群)丢失为灾难性损坏,您可以停止Pg,设置fsync=off,启动Pg,执行导入,然后(重要的)停止Pg并再次设置fsync=on。见WAL configuration。如果在PostgreSQL安装的任何数据库中已经存在任何您关心的数据,请不要这样做。如果你设置fsync=off你也可以设置full_page_writes=off;再次,请记住在导入后重新打开它以防止数据库损坏和数据丢失。请参阅Pg手册中的non-durable settings

您还应该考虑调整系统:

  • 尽可能使用高质量的SSD进行存储。具有可靠,受电源保护的回写高速缓存的良好SSD使得提交速度极快。当你遵循上面的建议 - 它减少磁盘冲洗/ fsync()s的数量 - 它们是不太有益的 - 但仍然可以是一个很大的帮助。除非您不关心保留数据,否则请勿在没有正确电源故障保护的情况下使用廉价的SSD。
  • 如果您使用RAID 5或RAID 6进行直接附加存储,请立即停止。备份数据,将RAID阵列重组为RAID 10,然后重试。 RAID 5/6对于批量写入性能毫无希望 - 尽管具有大缓存的优秀RAID控制器可以提供帮助。
  • 如果您可以选择使用具有大电池支持的回写高速缓存的硬件RAID控制器,则可以真正提高具有大量提交的工作负载的写入性能。如果您使用commit_delay进行异步提交,或者在批量加载期间执行较少的大事务,则无效。
  • 如果可能,将WAL(pg_xlog)存储在单独的磁盘/磁盘阵列上。在同一磁盘上使用单独的文件系统没什么意义。人们经常选择使用RAID1对来进行WAL。同样,这对具有高提交率的系统有更大的影响,如果您使用未记录的表作为数据加载目标,它几乎没有影响。

您可能也对Optimise PostgreSQL for fast testing感兴趣。


12
投票

根据文档使用COPY table TO ... WITH BINARY是“somewhat faster than the text and CSV formats”。如果您要插入数百万行,并且您对二进制数据感到满意,则只能执行此操作。

这是一个example recipe in Python, using psycopg2 with binary input


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,因此为了实现最快的插入,需要手动编写上述步骤。


7
投票

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

我不会列出所有不起作用的东西,直接切入肉中。

我在主目标表上删除了一个主键(这是一个GUID),我的30MI或行愉快地以每100K不到3秒的恒定速度流到目的地。


1
投票

为获得最佳插入性能,请禁用索引(如果这是您的选项)。除此之外,更好的硬件(磁盘,内存)也很有帮助


-1
投票

我也遇到了这个插入性能问题。我的解决方案是产生一些例程来完成插入工作。在此期间,SetMaxOpenConns应该被给予一个正确的数字,否则将提醒太多的开放连接错误。

db, _ := sql.open() 
db.SetMaxOpenConns(SOME CONFIG INTEGER NUMBER) 
var wg sync.WaitGroup
for _, query := range queries {
    wg.Add(1)
    go func(msg string) {
        defer wg.Done()
        _, err := db.Exec(msg)
        if err != nil {
            fmt.Println(err)
        }
    }(query)
}
wg.Wait()

我的项目的加载速度要快得多。这段代码只是简单介绍了它是如何工作的。读者应该能够轻松修改它。

© www.soinside.com 2019 - 2024. All rights reserved.