为什么Postgres UPDATE需要39个小时?


17

我有一个约210万行的Postgres表。我对其进行了以下更新:

WITH stops AS (
    SELECT id,
           rank() OVER (ORDER BY offense_timestamp,
                     defendant_dl,
                     offense_street_number,
                     offense_street_name) AS stop
    FROM   consistent.master
    WHERE  citing_jurisdiction=1
)

UPDATE consistent.master
SET arrest_id=stops.stop
FROM stops
WHERE master.id = stops.id;

该查询运行了39个小时。我在4(物理)核心i7 Q720笔记本电脑处理器上运行此程序,有足够的RAM,大多数时间没有其他运行。没有硬盘空间限制。该表最近已被清理,分析和重新索引。

在查询运行的整个过程中,至少在初始WITH完成后,CPU使用率通常较低,并且HDD的使用率为100%。HDD使用非常困难,以至于其他任何应用程序的运行速度都比正常运行慢得多。

笔记本电脑的电源设置为“ 高性能(Windows 7 x64)”。

这是说明:

Update on master  (cost=822243.22..1021456.89 rows=2060910 width=312)
  CTE stops
    ->  WindowAgg  (cost=529826.95..581349.70 rows=2060910 width=33)
          ->  Sort  (cost=529826.95..534979.23 rows=2060910 width=33)
                Sort Key: consistent.master.offense_timestamp, consistent.master.defendant_dl, consistent.master.offense_street_number, consistent.master.offense_street_name
                ->  Seq Scan on master  (cost=0.00..144630.06 rows=2060910 width=33)
                      Filter: (citing_jurisdiction = 1)
  ->  Hash Join  (cost=240893.51..440107.19 rows=2060910 width=312)
        Hash Cond: (stops.id = consistent.master.id)
        ->  CTE Scan on stops  (cost=0.00..41218.20 rows=2060910 width=48)
        ->  Hash  (cost=139413.45..139413.45 rows=2086645 width=268)
              ->  Seq Scan on master  (cost=0.00..139413.45 rows=2086645 width=268)

citing_jurisdiction=1仅排除数万行。即使有该WHERE条款,我仍然要处理超过200万行。

硬盘使用TrueCrypt 7.1a进行了整个驱动器加密。这会使速度变慢,但不足以使查询花费那么多小时。

WITH零件仅需花费3分钟即可运行。

arrest_id字段没有外键索引。该表上有8个索引和2个外键。查询中的所有其他字段都已建立索引。

arrest_id除了之外,该领域没有任何限制NOT NULL

该表共有32列。

arrest_id类型为characteristic(20)。我知道rank()产生一个数字值,但是我必须使用字符变化(20),因为我还有其他行citing_jurisdiction<>1针对该字段使用非数字数据。

arrest_id带有的所有行的字段均为空白citing_jurisdiction=1

这是一台个人高端笔记本电脑(截至1年前)。我是唯一的用户。没有其他查询或操作正在运行。锁定似乎不太可能。

该表中的任何地方或数据库中的其他任何地方都没有触发器。

此数据库上的其他操作不会花费大量时间。使用正确的索引,SELECT查询通常会很快。


这些Seq Scan有点吓人……
rogerdpack

Answers:


18

最近我有一个类似的事情,一张350万行的表。我的更新将永远不会完成。经过大量的尝试和挫折,我终于找到了罪魁祸首。原来是正在更新的表上的索引。

解决方案是在运行更新语句之前删除要更新的表上的所有索引。完成此操作后,更新将在几分钟内完成。更新完成后,我重新创建了索引并恢复了正常工作。目前这可能对您没有帮助,但可能其他人正在寻找答案。

我会将索引保留在要从中提取数据的表上。那将不必继续更新任何索引,并且应该有助于找到您要更新的数据。在速度较慢的笔记本电脑上运行良好。


3
我正在为您切换最佳答案。自从发布此内容以来,即使遇到要更新的列已经具有值且没有索引(!)的情况,我也遇到了其他问题,其中索引是问题。似乎Postgres在如何管理其他列上的索引方面存在问题。当对表的唯一更改是更新未索引的列并且您没有为该列的任何行增加分配的空间时,这些其他索引没有理由增加更新的查询时间。
Aren Cambre 2014年

1
谢谢!希望它能帮助别人。看起来很简单的事情可以为我省去数小时的头痛。
JC Avena

5
@ArenCambre-有一个原因:PostgreSQL将整行复制到其他位置,并将旧版本标记为已删除。这就是PostgreSQL实现多版本并发控制(MVCC)的方式。
Piotr Findeisen

我的问题是...为什么是罪魁祸首?另请参阅stackoverflow.com/a/35660593/32453
rogerdpack

15

您最大的问题是在笔记本电脑硬盘上进行大量写繁重,查找繁重的工作。无论您做什么,这都不会很快,特别是如果很多笔记本电脑中附带的那种速度较慢的5400RPM驱动器。

TrueCrypt的写入速度比“慢一点”放慢了很多。读取将相当快,但是写入使RAID 5看起来很快。在TrueCrypt卷上运行数据库将对写操作造成折磨,尤其是随机写操作。

在这种情况下,我认为您会浪费时间尝试优化查询。无论如何,您都在重写大多数行,并且由于令人恐惧的写入情况,它会很。我建议您:

BEGIN;
SELECT ... INTO TEMPORARY TABLE master_tmp ;
TRUNCATE TABLE consistent.master;
-- Now DROP all constraints on consistent.master, then:
INSERT INTO consistent.master SELECT * FROM master_tmp;
-- ... and re-create any constraints.

我怀疑这会比仅删除并重新创建约束要快,因为UPDATE将具有相当随机的写入模式,这将杀死您的存储。两个批量插入,一个插入到未记录的表中,一个插入到WAL记录的表中,没有限制,可能会更快。

如果您拥有绝对最新的备份,并且不介意从备份中还原数据库,则还可以使用fsync=off参数重新启动PostgreSQL 并full_page_writes=off 临时执行此批量操作。诸如断电或操作系统崩溃之类的任何意外问题都将使您的数据库无法恢复fsync=off

等同于“不记录”的POSTGreSQL使用未记录的表。如果数据库在脏的情况下不干净地关闭,则这些未记录的表将被截断。使用未记录的表将至少使您的写负载减半,并减少查找次数,因此它们可以快很多

像在Oracle中一样,最好删除索引然后在大批量更新后重新创建索引。PostgreSQL的计划者无法确定正在进行大的更新,暂停索引更新,然后在最后重建索引。即使有可能,也很难弄清楚在什么时候值得这样做,尤其是提前。


在大量写入和可怕的加密性能以及缓慢的笔记本电脑驱动器上可以找到答案。我还要指出的是,存在8个索引会产生许多额外的写入操作,并且会破坏HOT
内行

1
用fillfactor来提高HOT机会的好方法-尽管TrueCrypt强制在大块中强制块读写重写周期,但我不确定这是否有帮助。行迁移甚至可能更快,因为增长表至少是在进行线性写操作。
Craig Ringer 2012年

2.5年后,我正在做类似的事情,但是桌子更大。只是为了确保,即使我要更新的单列未建立索引,也删除所有索引是一个好主意吗?
Aren Cambre 2014年

1
@ArenCambre在这种情况下……很复杂。如果您的大多数更新都符合条件,HOT那么最好保留索引。如果不是,那么您可能会想要删除并重新创建。该列未建立索引,但是要进行HOT更新,同一页上也需要有可用空间,因此它取决于表中有多少死空间。如果主要是写操作,我会说删除所有索引。如果更新了很多,可能会有漏洞,您可能还可以。之类的工具pageinspect,并pg_freespacemap可以帮助确定这一点。
Craig Ringer 2014年

谢谢。在这种情况下,它是一个布尔列,已经在每行中都有一个条目。我正在某些行上更改条目。我刚刚确认:删除所有索引后,更新仅用了2个小时。事先,我必须在18小时后停止更新,因为更新时间太长。尽管事实上正在更新的列肯定没有索引。
Aren Cambre 2014年

2

有人会为Postgres给出更好的答案,但是从Oracle的角度来看,这里有一些发现可能适用(注释对于注释字段来说太长了)。

我首先要考虑的是在一次交易中更新200万行。在Oracle中,您将为每个要更新的块编写一个前映像,以便其他会话仍保持一致的读取状态,而无需读取已修改的块,并且可以回滚。那是建立了很长的回滚。通常情况下,您最好进行小批量交易。一次说出1,000条记录。

如果表上有索引,并且在维护期间表将被视为无法使用,那么通常最好在进行大操作之前先删除索引,然后再重新创建它。然后便宜地不断尝试维护每个更新记录的索引。

Oracle允许对语句的“无日志记录”提示来停止日志记录。它大大加快了语句的速度,但使数据库处于“不可恢复”的状态。因此,您需要先备份,然后再备份。我不知道Postgres是否有类似的选择。


PostgreSQL没有长时间回滚的问题,不存在。无论您的事务有多大,ROLBACK在PostgreSQL中都非常快。Oracle!= PostgreSQL
Frank Heikens 2012年

@FrankHeikens谢谢,这很有趣。我将不得不阅读日记在Postgres上的工作方式。为了使事务的整个概念起作用,在事务期间需要以某种方式维护数据的两个不同版本,即前映像和后映像,这就是我所指的机制。一种或另一种方式,我认为存在一个阈值,超过该阈值来维持事务的资源将太昂贵。
Glenn 2012年

2
@Glenn postgres在表本身中保留行的版本-请参阅此处以获取解释。折衷方案是使“死”元组四处徘徊,这些元组可以用postgres中的“真空”进行异步清理(Oracle不需要清理,因为它本身在表中从来没有“死”行)
Jack说试试topanswers.xyz 2012年


@Glenn有关PostgreSQL行版本并发控制的规范文档是postgresql.org/docs/current/static/mvcc-intro.html,非常值得一读。另请参阅wiki.postgresql.org/wiki/MVCC。请注意,带有死行的MVCC VACUUM只是答案的一半;PostgreSQL还使用所谓的“预写日志”(实际上是日志)来提供原子提交并防止部分写操作,等等。请参见postgresql.org/docs/current/static/wal-intro.html
Craig Ringer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.