为什么 Postgres UPDATE 需要 39 小时?

Are*_*bre 20 postgresql

我有一个包含约 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;
Run Code Online (Sandbox Code Playgroud)

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

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

笔记本电脑的电源设置为高性能(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)
Run Code Online (Sandbox Code Playgroud)

citing_jurisdiction=1只排除几万行。即使有这个WHERE条款,我仍然在处理超过 200 万行。

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

WITH部分只需大约 3 分钟即可运行。

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

arrest_id领域没有限制,除了NOT NULL

该表共有 32 列。

arrest_id是类型character variables(20)。我意识到rank()产生一个数值,但我必须使用字符变化(20),因为我有其他行在citing_jurisdiction<>1该字段中使用非数字数据。

arrest_id对于所有带有 的行,该字段为空白citing_jurisdiction=1

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

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

此数据库上的其他操作永远不会花费大量时间。通过适当的索引,SELECT查询通常非常快。

小智 29

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

解决方案是在运行更新语句之前删除正在更新的表上的所有索引。一旦我这样做了,更新会在几分钟内完成。更新完成后,我重新创建了索引并重新开始工作。在这一点上,这可能对您没有帮助,但可能会有其他人在寻找答案。

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

  • @ArenCambre——这是有原因的:PostgreSQL 将整行复制到不同的位置,并将旧版本标记为已删除。这就是 PostgreSQL 实现多版本并发控制 (MVCC) 的方式。 (6认同)
  • 我把最佳答案转给你。自从我发布这篇文章以来,我遇到了索引是问题的其他情况,即使正在更新的列已经有一个值并且没有索引(!)。Postgres 似乎在如何管理其他列上的索引方面存在问题。当对表的唯一更改是更新未索引的列并且您没有增加该列的任何行的分配空间时,这些其他索引没有理由增加更新的查询时间。 (3认同)

Cra*_*ger 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.
Run Code Online (Sandbox Code Playgroud)

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

如果您有绝对最新的备份并且不介意必须从备份中恢复您的数据库,您还可以使用fsync=off参数重新启动 PostgreSQL,并且full_page_writes=off 临时用于此批量操作。任何意外问题(如断电或操作系统崩溃)都会使您的数据库在fsync=off.

相当于“无日志记录”的 POSTGreSQL 是使用未记录的表。如果数据库在脏时不干净地关闭,这些未记录的表将被截断。使用未注册的表将至少减半你的写入负载,减少寻道的次数,这样他们就可以成为一个LOT更快。

就像在 Oracle 中一样,删除索引然后在大批量更新后重新创建它可能是个好主意。PostgreSQL 的规划器无法计算出正在发生大更新,暂停索引更新,然后在最后重建索引;即使可以,它也很难弄清楚在什么时候值得这样做,尤其是提前。