Postgresql 截断速度

Posted

技术标签:

【中文标题】Postgresql 截断速度【英文标题】:Postgresql Truncation speed 【发布时间】:2012-07-10 07:07:21 【问题描述】:

我们使用Postgresql 9.1.4 作为我们的数据库服务器。我一直在努力加快我的测试套件的速度,所以我盯着数据库分析了一下,看看到底发生了什么。我们使用database_cleaner 在测试结束时截断表。是的,我知道交易速度更快,但在某些情况下我无法使用它们,所以我不担心。

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

现在,在本地(在 Macbook Air 上)一个完整的测试套件需要 28 分钟。拖尾日志,每次我们截断表时......即:

TRUNCATE TABLE table1, table2  -- ... etc

执行截断需要 1 秒以上。在我们的 CI 服务器(Ubuntu 10.04 LTS)上跟踪日志,截断表需要整整 8 秒,构建需要 84 分钟。

当我切换到 :deletion 策略时,我的本地构建需要 20 分钟,而 CI 服务器下降到 44 分钟。这是一个显着的差异,我真的很惊讶为什么会这样。我在 CI 服务器上有 tuned the DB,它有 16gb 系统内存、4gb shared_buffers...和一个 SSD。所有的好东西。怎么可能:

a. 它比我的带有 2gb 内存的 Macbook Air 慢得多b. 当postgresql docs 时,TRUNCATION 比 DELETE 慢得多state explicitly 应该更快。

有什么想法吗?

【问题讨论】:

你是在 macbook 上运行测试和数据库,在 CI 服务器上运行测试和数据库吗?测试和数据库是否在同一台机器上? 顺便说一句,你做错了......你无法在测试后清除数据库。您应该在运行测试之前执行此操作。您无法确定测试后数据库是否已清除。 使用了哪些 postgresql.conf 参数?我想知道您是否使用 fsync=off 运行(如果您不介意丢失所有数据,例如在测试中),在这种情况下,DELETETRUNCATE 之间的平衡可能会有所不同。我也对你的shared_buffers 感兴趣。 当你的意思是“使用事务”时,你是指打开一个事务,做一些测试,然后回滚吗?因为在我看来,这只是测试的一半。如果您使用SERIALIZABLE 事务、DEFERRABLE INITIALLY DEFERRED 约束等,那么在COMMIT 时间会发生很多事情,提交测试更改似乎是明智的。 顺便说一句,“etc”有多长,即一次有多少表被截断?它们是非常小的表,还是包含一些数据?如果小表比TRUNCATE 更快DELETE FROM,我不会感到太震惊,因为TRUNCATE 必须分配一个新的支持文件,写入标题,用它交换旧的,刷新缓冲区缓存对于表,和fsync。我怀疑文档可能需要更新以反映 TRUNCATE 使用大表要快得多,但不一定是小/空的。 【参考方案1】:

我最近遇到了类似的问题,即:

    运行使用 DatabaseCleaner 的测试套件的时间在具有可比硬件的不同系统之间差异很大, 将 DatabaseCleaner 策略更改为 :deletion 提供了约 10 倍的改进。

缓慢的根本原因是用于数据库存储的带有日志 (ext4) 的文件系统。在 TRUNCATE 操作期间,日志守护程序 (jbd2) 使用了大约 90% 的磁盘 IO 容量。在这些情况下,我不确定这是错误、边缘情况还是实际上的正常行为。然而,这解释了为什么 TRUNCATE 比 DELETE 慢得多——它产生了更多的磁盘写入。由于我不想实际使用 DELETE,所以我求助于设置 fsync=off,这足以缓解这个问题(在这种情况下,数据安全并不重要)。

【讨论】:

【参考方案2】:

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

最后两点的TL;DR

(a) 较大的 shared_buffers 可能是 CI 服务器上 TRUNCATE 较慢的原因。不同的 fsync 配置或使用旋转介质而不是 SSD 也可能有问题。

(b) TRUNCATE 具有固定成本,但不一定比 DELETE 慢,而且它的工作量更大。请参阅下面的详细说明。

更新:significant discussion on pgsql-performance 来自这篇帖子。见this thread。

更新 2: 已向 9.2beta3 添加了改进,应该有助于解决此问题,请参阅 this post。

详细解释TRUNCATE vs DELETE FROM

虽然不是该主题的专家,但我的理解是TRUNCATE 每张表的成本几乎是固定的,而DELETE 对于 n 行至少是 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 为您提供一个全新的表和索引,就好像它们只是 CREATEed。就像您删除了所有记录,重新索引表并执行了VACUUM FULL

如果您不关心表中是否还有残留物,因为您要再次将其填满,您最好使用DELETE FROM table;

因为您没有运行VACUUM,您会发现死行和索引条目累积为必须扫描然后忽略的膨胀;这会减慢您的所有查询速度。如果您的测试实际上并没有创建和删除您可能不会注意到或关心的所有数据,那么您可以随时执行VACUUM 或在测试运行过程中执行两次。更好的是,让积极的 autovacuum 设置确保 autovacuum 在后台为您执行此操作。

您仍然可以在 整个 测试套件运行后 TRUNCATE 所有表,以确保在多次运行中不会产生任何影响。在 9.0 及更高版本中,全局上的VACUUM (FULL, ANALYZE); 至少与此一样好,如果不是更好的话,而且要容易得多。

IIRC Pg 有一些优化,这意味着它可能会注意到您的事务是唯一可以看到表的事务,并立即将块标记为空闲。在测试中,当我想创建膨胀时,我必须有多个并发连接才能做到这一点。不过,我不会依赖这个。

DELETE FROM table; 对于没有 f/k refs 的小桌子来说非常便宜

对于DELETE一个表中没有外键引用的所有记录,所有Pg都必须进行顺序表扫描并设置遇到的元组的xmax。这是一个非常便宜的操作——基本上是线性读取和半线性写入。 AFAIK 它不必触及索引;它们继续指向死元组,直到它们被后来的VACUUM 清理,这也将表中仅包含死元组的块标记为空闲。

DELETE 仅在有 很多 条记录、必须检查大量外键引用或者您计算匹配 @987654357 所需的后续 VACUUM (FULL, ANALYZE) table; 时才会变得昂贵@ 的影响在您的 DELETE 的成本范围内。

在我的测试中,DELETE FROM table; 在 0.5 毫秒和 2 毫秒时通常比 TRUNCATE 快 4 倍。这是 SSD 上的测试数据库,使用 fsync=off 运行,因为我不在乎是否丢失所有这些数据。当然,DELETE FROM table; 并没有做同样的工作,如果我跟进VACUUM (FULL, ANALYZE) table;,它的 21 毫秒要贵得多,所以DELETE 只有在我实际上不需要桌子的情况下才是一个胜利原始的。

TRUNCATE table;DELETE 做了更多的固定成本工作和内务管理

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

我最近了解到TRUNCATE 还必须刷新所有与旧表相关的 PostgreSQL 缓冲区。对于巨大的shared_buffers,这可能需要相当长的时间。我怀疑这就是你的 CI 服务器上速度较慢的原因。

平衡

无论如何,您可以看到具有关联 TOAST 表(大多数都这样做)和多个索引的表的 TRUNCATE 可能需要一些时间。不长,但比一张近乎空的桌子上的DELETE 还要长。

因此,您最好使用DELETE FROM table;

--

注意:在 9.0 之前的 DB 上,CLUSTER table_id_seq ON table; ANALYZE table;VACUUM FULL ANALYZE table; REINDEX table; 将更接近于 TRUNCATEVACUUM FULL impl 在 9.0 中更改为更好的。

【讨论】:

它们也有不同类型的锁:表锁和行锁。 感谢您的全面回答!根据文档>>它 [TRUNCATE] 与每个表上的不合格 DELETE 具有相同的效果,但由于它实际上并不扫描表,因此速度更快。此外,它会立即回收磁盘空间,而不需要后续的 VACUUM 操作。 >> 所以我不认为它在截断后实际上是真空的。您是否还暗示我拥有 4GB 共享缓冲区这一事实实际上会损害性能? @brad 对于TRUNCATE 的具体情况,是的,我的理解是,大shared_buffers 可能会减慢速度。我自己没有对此进行测试,但这就是 ML 讨论中的声音。不,在截断之后没有完成 VACCUM - 虽然截断 具有 DELETE FROM 后跟 VACUUM FULL ANALYZE; 的效果,但它实际上并没有以这种方式工作或执行这些步骤. 感谢您提供指向该 ML 顺便说一句的链接...很高兴看到围绕此类主题的对话 @BrianPreslopsky 回滚。如果您的 txn 中止、服务器或客户端崩溃等。噗,数据消失了。【参考方案3】:

布拉德,只是想让你知道。我已经相当深入地研究了一个非常相似的问题。

相关问题:30 tables with few rows - TRUNCATE the fastest way to empty them and reset attached sequences?

请同时查看这个问题和这个拉取请求:

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

我很抱歉写这个作为答案,但我没有找到任何评论链接,可能是因为那里已经有太多的 cmets。

【讨论】:

嘿,谢谢 stanislaw。我实际上看到了那些提示我升级数据库清理器以使用批量截断的帖子。然而,这对我帮助不大。仍然在 PG 上,删除策略似乎明显更快,这是我最终使用的。【参考方案4】:

需要考虑的几种替代方法:

创建一个包含静态“夹具”数据的空数据库,并在其中运行测试。完成后,只需删除数据库,这应该很快。 创建一个名为“test_ids_to_delete”的新表,其中包含表名和主键 ID 列。更新您的删除逻辑以在此表中插入 ids/表名称,这将比运行删除快得多。然后,编写一个脚本以“离线”运行以实际删除数据,无论是在整个测试运行完成后,还是在一夜之间。

前者是“无尘室”方法,而后者意味着会有一些测试数据会在数据库中保留更长时间。带有脱机删除的“脏”方法是我用于具有大约 20,000 个测试的测试套件的方法。是的,由于开发数据库中有“额外”的测试数据,有时会出现问题。但有时这种“肮脏”帮助我们找到并修复了错误,因为“混乱”更好地模拟了现实世界的情况,以一种洁净室方法永远不会的方式。

【讨论】:

以上是关于Postgresql 截断速度的主要内容,如果未能解决你的问题,请参考以下文章

python postgresql orm 截断表

如何在 PostgreSql 上使用 Jooq 级联截断?

tds_fdw PostgreSQL 外部表将大文本截断为 2048 个字符

如何提高postgresql的插入速度

四、PostgreSQL常用数据类型(精简)

带有大表的 Geoserver WFS + PostgreSQL 速度极慢