我正在测试Postgres插入性能。我有一个表,其中有一列,其数据类型为数字。还有一个索引。我用这个查询填充了数据库:
insert into aNumber (id) values (564),(43536),(34560) ...
我非常快地插入了400万行,使用上面的查询一次插入10,000行。在数据库达到600万行之后,性能急剧下降到每15分钟100万行。有什么技巧可以提高插入性能吗?我需要在这个项目上的最佳插入性能。
在内存为5gb的机器上使用Windows 7 Pro。
为了获得最佳的插入性能,如果可以选择禁用索引。除此之外,更好的硬件(磁盘、内存)也很有帮助
参见PostgreSQL手册中的填充数据库,该主题的Depesz一如既往的优秀文章和这个SO问题。
(请注意,这个答案是关于批量加载数据到现有的DB或创建一个新的DB。如果你对pg_restore或__ABC1执行__ABC2输出的DB恢复性能感兴趣,其中大部分不适用,因为__ABC2和pg_restore已经在完成模式+数据恢复后创建触发器和索引之类的事情)。
pg_restore
有很多事情要做。理想的解决方案是导入到一个没有索引的UNLOGGED表中,然后将其更改为logged并添加索引。不幸的是,在PostgreSQL 9.4中不支持将表从UNLOGGED更改为logged。9.5添加ALTER TABLE ... SET LOGGED来允许你这样做。
UNLOGGED
ALTER TABLE ... SET LOGGED
如果可以让数据库脱机进行批量导入,请使用pg_bulkload。
pg_bulkload
否则:
禁用表上的任何触发器
导入前删除索引,导入后重新创建索引。(多在一次传递中构建索引所花费的时间比逐步向其添加相同的数据所花费的时间更少,并且所得到的索引更紧凑)。
如果可能,使用COPY代替INSERTs
COPY
INSERT
如果你不能使用COPY,考虑使用多值的INSERTs。你好像已经开始这么做了。不要试图在一个VALUES中列出太多个值;这些值必须多次适合内存,所以每条语句保持几百个。
VALUES
synchronous_commit=off
commit_delay
INSERT或COPY从多个连接并行。有多少取决于硬件的磁盘子系统;根据经验,如果使用直接连接的存储,则每个物理硬盘驱动器需要一个连接。
设置一个较高的max_wal_size值(旧版本中的checkpoint_segments)并启用log_checkpoints。查看PostgreSQL日志,确保它没有抱怨检查点出现得太频繁。
max_wal_size
checkpoint_segments
log_checkpoints
fsync=off
fsync=on
full_page_writes=off
你还应该考虑优化你的系统:
尽可能使用质量好 ssd盘进行存储。具有可靠的、电源保护的回写缓存的优秀ssd使提交速度快得令人难以置信。当你遵循上面的建议(减少磁盘刷新/ __abc0的数量)时,它们就不那么有益了,但仍然可以提供很大的帮助。不要使用没有适当的掉电保护的廉价ssd,除非你不在乎保存你的数据。
pg_wal
pg_xlog
你可能也对优化PostgreSQL进行快速测试感兴趣。
使用COPY table TO ... WITH BINARY根据文档"比文本和CSV格式略快."只有当您有数百万行要插入,并且您对二进制数据感到满意时才这样做。
COPY table TO ... WITH BINARY
这是一个Python中的示例食谱,使用psycopg2和二进制输入。
除了Craig Ringer的文章和depesz的博客文章外,如果你想通过在事务中使用预处理语句插入来加快通过ODBC (psqlodbc)接口的插入速度,你还需要做一些额外的事情来让它快速工作:
Protocol=-1
UseServerSidePrepare=1
SQLSetConnectAttr(conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast<SQLPOINTER>(SQL_AUTOCOMMIT_OFF), 0);
SQLEndTran(SQL_HANDLE_DBC, conn, SQL_COMMIT);
不幸的是,psqlodbc通过发出一系列未准备好的插入语句来“实现”SQLBulkOperations,因此为了实现最快的插入,需要手动编写上述步骤。
SQLBulkOperations
我也遇到了这个插入性能问题。我的解决方案是衍生一些go例程来完成插入工作。同时,SetMaxOpenConns应该被赋予一个合适的数字,否则会有太多的打开连接错误被警告。
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()
对于我的项目,加载速度要快得多。这段代码片段只是给出了它的工作原理。读者应该能够轻松地修改它。
我今天花了大约6个小时在同一个问题上。插入以“常规”速度(每100K小于3秒)进行,直到5MI(总共30MI)行,然后性能急剧下降(一直下降到每100K 1分钟)。
我不会列出所有不起作用的事情,直接切入正题。
我在目标表(这是一个GUID)上丢失主键,我的30MI或行愉快地以每100K不到3秒的恒定速度流到目的地。
如果你碰巧插入带有uuid的列(这不是完全你的情况)并添加到@Dennis 回答(我还不能评论),建议使用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)
而且,这是建议的官方方式
请注意 如果您只需要随机生成的uuid(版本4),可以考虑使用pgcrypto模块中的gen_random_uuid()函数。
请注意
如果您只需要随机生成的uuid(版本4),可以考虑使用pgcrypto模块中的gen_random_uuid()函数。
这将3.7M行的插入时间从大约2小时降低到大约10分钟。