codecamp

PostgreSQL 填充一个数据库

14.4.1. 禁用自动提交
14.4.2. 使用COPY
14.4.3. 移除索引
14.4.4. 移除外键约束
14.4.5. 增加maintenance_work_mem
14.4.6. 增加max_wal_size
14.4.7. 禁用 WAL 归档和流复制
14.4.8. 事后运行ANALYZE
14.4.9. 关于pg_dump的一些注记

第一次填充数据库时可能需要插入大量的数据。本节包含一些如何让这个处理尽可能高效的建议。

14.4.1. 禁用自动提交

在使用多个INSERT时,关闭自动提交并且只在最后做一次提交(在普通 SQL 中,这意味着在开始发出BEGIN并且在结束时发出COMMIT。某些客户端库可能背着你就做了这些,在这种情况下你需要确定在你需要做这些时该库确实帮你做了)。如果你允许每一个插入都被独立地提交,PostgreSQL要为每一个被增加的行做很多工作。在一个事务中做所有插入的一个额外好处是:如果一个行的插入失败则所有之前插入的行都会被回滚,这样你不会被卡在部分载入的数据中。

14.4.2. 使用COPY

使用COPY在一条命令中装载所有记录,而不是一系列INSERT命令。 COPY命令是为装载大量行而优化过的; 它没INSERT那么灵活,但是在大量数据装载时导致的负荷也更少。 因为COPY是单条命令,因此使用这种方法填充表时无须关闭自动提交。

如果你不能使用COPY,那么使用PREPARE来创建一个预备INSERT语句也有所帮助,然后根据需要使用EXECUTE多次。这样就避免了重复分析和规划 INSERT的负荷。不同接口以不同的方式提供该功能, 可参阅接口文档中的预备语句

请注意,在载入大量行时,使用COPY几乎总是比使用INSERT快, 即使使用了PREPARE并且把多个插入被成批地放入一个单一事务。

同样的事务中,COPY比更早的CREATE TABLETRUNCATE命令更快。 在这种情况下,不需要写 WAL,因为在一个错误的情况下,包含新载入数据的文件不管怎样都将被移除。不过,只有当wal_level设置为 minimal(此时所有的命令必须写 WAL)时才会应用这种考虑。

14.4.3. 移除索引

如果你正在载入一个新创建的表,最快的方法是创建该表,用COPY批量载入该表的数据,然后创建表需要的任何索引。在已存在数据的表上创建索引要比在每一行被载入时增量地更新它更快。

如果你正在对现有表增加大量的数据,删除索引、载入表然后重新创建索引可能是最好的方案。 当然,在缺少索引的期间,其它数据库用户的数据库性能将会下降。 我们在删除唯一索引之前还需要仔细考虑清楚,因为唯一约束提供的错误检查在缺少索引的时候会丢失。

14.4.4. 移除外键约束

和索引一样,成批地检查外键约束比一行行检查效率更高。 因此,先删除外键约束、载入数据然后重建约束会很有用。 同样,载入数据和约束缺失期间错误检查的丢失之间也存在平衡。

更重要的是,当你在已有外键约束的情况下向表中载入数据时, 每个新行需要一个在服务器的待处理触发器事件(因为是一个触发器的触发会检查行的外键约束)列表的条目。载入数百万行会导致触发器事件队列溢出可用内存, 造成不能接受的交换或者甚至是命令的彻底失败。因此在载入大量数据时,可能需要(而不仅仅是期望)删除并重新应用外键。如果临时移除约束不可接受,那唯一的其他办法可能是就是将载入操作分解成更小的事务。

14.4.5. 增加maintenance_work_mem

在载入大量数据时,临时增大maintenance_work_mem配置变量可以改进性能。这个参数也可以帮助加速CREATE INDEX命令和ALTER TABLE ADD FOREIGN KEY命令。 它不会对 COPY本身起很大作用,所以这个建议只有在你使用上面的一个或两个技巧时才有用。

14.4.6. 增加max_wal_size

临时增大max_wal_size配置变量也可以让大量数据载入更快。 这是因为向PostgreSQL中载入大量的数据将导致检查点的发生比平常(由checkpoint_timeout配置变量指定)更频繁。无论何时发生一个检查点时,所有脏页都必须被刷写到磁盘上。 通过在批量数据载入时临时增加max_wal_size,所需的检查点数目可以被缩减。

14.4.7. 禁用 WAL 归档和流复制

当使用 WAL 归档或流复制向一个安装中载入大量数据时,在录入结束后执行一次新的基础备份比处理大量的增量 WAL 数据更快。为了防止载入时记录增量 WAL,通过将wal_level设置为minimal、将archive_mode设置为 off以及将max_wal_senders设置为零来禁用归档和流复制。 但需要注意的是,修改这些设置需要重启服务。

除了避免归档器或 WAL 发送者处理 WAL 数据的时间之外,这样做将实际上使某些命令更快,因为如果wal_levelminimal并且当前子事务(或顶级事务)创建或截断了它们更改的表或索引,则它们根本不编写 WAL。(通过在最后执行一个fsync而不是写 WAL,它们能以更小地代价保证崩溃安全)。

14.4.8. 事后运行ANALYZE

不管什么时候你显著地改变了表中的数据分布后,我们都强烈推荐运行ANALYZE。着包括向表中批量载入大量数据。运行ANALYZE(或者VACUUM ANALYZE)保证规划器有表的最新统计信息。 如果没有统计数据或者统计数据过时,那么规划器在查询规划时可能做出很差劲决定,导致在任意表上的性能低下。需要注意的是,如果启用了 autovacuum 守护进程,它可能会自动运行ANALYZE;参阅第 24.1.3 节第 24.1.6 节

14.4.9. 关于pg_dump的一些注记

pg_dump生成的转储脚本自动应用上面的若干个(但不是全部)技巧。 要尽可能快地载入pg_dump转储,你需要手工做一些额外的事情(请注意,这些要点适用于恢复一个转储,而不是创建它的时候。同样的要点也适用于使用 psql载入一个文本转储或用pg_restore从一个pg_dump归档文件载入)。

默认情况下,pg_dump使用COPY,并且当它在生成一个完整的模式和数据转储时, 它会很小心地先装载数据,然后创建索引和外键。因此在这种情况下,一些指导方针是被自动处理的。你需要做的是:

  • maintenance_work_memmax_wal_size设置适当的(即比正常值大的)值。

  • 如果使用 WAL 归档或流复制,在转储时考虑禁用它们。在载入转储之前,可通过将archive_mode设置为off、将wal_level设置为minimal以及将max_wal_senders设置为零(在录入dump前)来实现禁用。 之后,将它们设回正确的值并执行一次新的基础备份。

  • 采用pg_dumppg_restore的并行转储和恢复模式进行实验并且找出要使用的最佳并发任务数量。通过使用-j选项的并行转储和恢复应该能为你带来比串行模式高得多的性能。

  • 考虑是否应该在一个单一事务中恢复整个转储。要这样做,将-1--single-transaction命令行选项传递给psqlpg_restore。 当使用这种模式时,即使是一个很小的错误也会回滚整个恢复,可能会丢弃已经处理了很多个小时的工作。根据数据间的相关性, 可能手动清理更好。如果你使用一个单一事务并且关闭了 WAL 归档,COPY命令将运行得最快。

  • 如果在数据库服务器上有多个 CPU 可用,可以考虑使用pg_restore--jobs选项。这允许并行数据载入和索引创建。

  • 之后运行ANALYZE

一个只涉及数据的转储仍将使用COPY,但是它不会删除或重建索引,并且它通常不会触碰外键。 [13] 因此当载入一个只有数据的转储时,如果你希望使用那些技术,你需要负责删除并重建索引和外键。在载入数据时增加max_wal_size仍然有用,但是不要去增加 maintenance_work_mem;不如说在以后手工重建索引和外键时你已经做了这些。并且不要忘记在完成后执行ANALYZE,详见第 24.1.3 节第 24.1.6 节



[13] 你可以通过使用--disable-triggers选项的方法获得禁用外键的效果 — 不过你要意识到这么做是消除(而不只是推迟)外键验证。因此如果你使用该选项,就可能插入坏数据。


PostgreSQL 用显式JOIN子句控制规划器
PostgreSQL 非持久设置
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

PostgreSQL SQL语言

PostgreSQL 服务器管理

PostgreSQL 客户端接口

PostgreSQL 服务器编程

PostgreSQL 参考

PostgreSQL 内部

PostgreSQL 附录

PostgreSQL 参考书目

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }