Postgresql截断速度

我们使用Postgresql 9.1.4作为我们的数据库服务器。 我一直在试图加速我的testing套件,所以我已经盯着分析分贝来看看到底发生了什么。 我们使用database_cleaner在testing结束时截断表。 是的,我知道交易更快,我不能在某些情况下使用它们,所以我不关心这一点。

我所关心的是,为什么TRUNCATION需要这么长时间(比使用DELETE时间长),为什么在我的CI服务器上需要更长的时间。

目前,在本地(在Macbook Air上)一个完整的testing套件需要28分钟。 拖尾日志,每次我们截断表…即:

 TRUNCATE TABLE table1, table2 -- ... etc 

执行截断需要1秒多的时间。 拖拽我们的CI服务器(Ubuntu 10.04 LTS)上的日志,需要8秒钟的时间来截断表,而构build需要84分钟。

当我切换到:deletion策略时,我的本地构build花了20分钟,CI服务器下降到44分钟。 这是一个重大的差异,我真的被吹走,为什么这可能是。 我调整 了 CI服务器上的数据库,它有16GB的系统RAM,4GB的shared_buffers …和一个SSD。 所有的好东西。 这怎么可能:

一个。 这比我的MacBook Air和2gb的RAM要慢很多
那个TRUNCATION比DELETE慢得多,当postgresql文档 明确指出它应该快得多。

有什么想法吗?

在SO和PostgreSQL邮件列表上,最近都出现了几次。

TL; DR最后两点:

(a)较大的shared_buffers可能是CI服务器上TRUNCATE速度较慢的原因。 不同的fsyncconfiguration或使用旋转媒体代替SSD也可能是错误的。

(b) TRUNCATE有一个固定的成本,但不一定比DELETE慢,另外还有更多的工作。 请参阅下面的详细说明。

更新: 关于pgsql性能的重要讨论来自这篇文章。 看到这个线程 。

更新2: 9.2beta3已经增加了改进,应该有助于这个,看到这个职位 。

详细的说明TRUNCATEDELETE FROM

虽然不是这个主题的专家,但我的理解是, TRUNCATE每个表的固定成本几乎是固定的,而对于n行, DELETE至less是O(n); 如果有任何引用表的外键被删除,情况会更糟。

我总是假设一个TRUNCATE的固定成本低于DELETE在几乎空的表上的成本,但这根本不是真的。

TRUNCATE table; 做多于DELETE FROM table;

TRUNCATE table之后的数据库状态与您要运行的情况非常相似:

  • DELETE FROM table;
  • VACCUUM (FULL, ANALYZE) table; (仅限9.0+,请参阅脚注)

…虽然当然TRUNCATE实际上并没有实现DELETEVACUUM

重点是DELETETRUNCATE做不同的事情,所以你不只是比较两个命令相同的结果。

DELETE FROM table; 允许保留死行和膨胀,允许索引携带死条目,不更新查询计划器使用的表统计信息等。

TRUNCATE为您提供了一个全新的表格和索引,就像它们只是CREATE ed一样。 这就像你删除了所有logging,重新索引表格,并做了一个VACUUM FULL

如果你不关心桌子上是否有残留物,因为你将要再次填满,那么使用DELETE FROM table;可能会更好DELETE FROM table;

因为您没有运行VACUUM您会发现死行和索引条目会累积为一个必须扫描然后被忽略的膨胀; 这会降低您的所有查询速度。 如果你的testing没有真正创build和删除你可能没有注意到或者关心的所有数据,并且你可以在你的testing运行中通过VACUUM或者两个部分进行testing。 更好的是,让积极的autovacuum设置确保autovacuum在后台为您做。

整个testing套件运行之后,仍然可以对所有表格进行TRUNCATE ,以确保在许多运行中不会形成任何效果。 在9.0和更新版本上, VACUUM (FULL, ANALYZE); 在桌面上的全球化至less是一样好,如果不是更好,这是一件容易的事情。

IIRC Pg有一些优化,这意味着它可能会注意到,当您的交易是唯一可以看到表格并立即将块标记为空闲的时候。 在testing中,当我想创build膨胀时,我必须有多个并发连接才能完成。 不过,我不会依赖这个。

DELETE FROM table; 对于没有f / k参考的小桌子是非常便宜的

要从表中DELETE没有外键引用的所有logging,所有Pg必须执行顺序表扫描并设置遇到的元组的xmax 。 这是一个非常便宜的操作 – 基本上是线性读取和半线性写入。 AFAIK它不必触摸指标; 他们继续指向死亡的元组,直到它们被后面的VACUUM清除,这个VACUUM也标志着表中只包含死元组的空闲块。

如果有很多logging,如果有很多必须检查的外键引用,或者如果您计算后续的VACUUM (FULL, ANALYZE) table; 需要在DELETE的代价内匹配TRUNCATE的效果。

在我的testing中, DELETE FROM table; 在0.5ms与2ms之间通常比TRUNCATE快4倍。 这是SSD上的testing数据库,运行fsync=off因为我不在乎是否丢失了所有这些数据。 当然, DELETE FROM table; 是不是在做同样的工作,如果我跟进一个VACUUM (FULL, ANALYZE) table; 这是一个更昂贵的21ms,所以如果我实际上并不需要原始表,那么DELETE只是一个胜利。

TRUNCATE table; 做比DELETE更多的固定成本的工作和家务

相比之下, TRUNCATE必须做很多工作。 它必须为表,它的TOAST表(如果有的话)以及表的每个索引分配新的文件。 头文件必须写入这些文件,系统目录可能也需要更新(不确定在这一点上,没有检查)。 然后它必须用旧的文件replace旧的文件或删除旧的文件,并且必须通过同步操作fsync()或类似的操作来确保文件系统已经赶上了所做的更改,通常将所有缓冲区刷新到磁盘。 我不确定如果您正在使用(数据fsync=off )选项fsync=off是否跳过同步。

我最近了解到, TRUNCATE还必须刷新所有与旧表相关的PostgreSQL缓冲区。 这可能需要大量的shared_buffers时间。 我怀疑这是为什么它在你的CI服务器上变慢。

余额

无论如何,你可以看到有一个关联的TOAST表(大部分是)和几个索引的TRUNCATE可能需要一些时间。 不长,但比从近乎空的表格中DELETE更长。

因此,你最好做一个DELETE FROM table;

注意:在9.0以前的DB上, CLUSTER table_id_seq ON table; ANALYZE table; CLUSTER table_id_seq ON table; ANALYZE table;VACUUM FULL ANALYZE table; REINDEX table; VACUUM FULL ANALYZE table; REINDEX table; 将会更接近TRUNCATEVACUUM FULL IMPL在9.0中变得更好了。

布莱德,只是为了让你知道。 我已经深入了解了一个非常类似的问题。

相关的问题: 30行,几行 – TRUNCATE清空它们,并重置附加序列的最快方法?

请看这个问题和这个拉请求:

https://github.com/bmabey/database_cleaner/issues/126

https://github.com/bmabey/database_cleaner/pull/127

另外这个线程: http : //archives.postgresql.org/pgsql-performance/2012-07/msg00047.php

我很抱歉写这个答案,但我没有find任何评论链接,也许是因为已经有太多的评论。

一些替代方法要考虑:

  • 用静态“夹具”数据创build一个空的数据库,然后运行testing。 完成后,只需放下数据库,该数据库应该很快。
  • 创build一个名为“test_ids_to_delete”的新表,其中包含表名和主键标识的列。 更新你的删除逻辑来代替在这个表中插入id /表名,这比运行删除要快得多。 然后,编写一个脚本来运行“脱机”,以便在整个testing运行完成之后或者一夜之间实际删除数据。

前者是“洁净室”的做法,后者则意味着将有一些testing数据在数据库中持续时间更长。 离线删除的“肮脏”的方法是我用于testing套件约20,000testing。 是的,由于在开发数据库中有“额外的”testing数据,有时候会有问题。 但有时这种“肮脏”帮助我们find并修正了错误,因为“混乱”更好地模拟了现实世界中的情况,而洁净室的方法永远不会。