7

我有以下UPDATE查询:

UPDATE Indexer.Pages SET LastError=NULL where LastError is not null;

目前,此查询大约需要 93 分钟才能完成。我想找到让这更快一点的方法。

Indexer.Pages表有大约 506,000 行,其中大约 490,000 行包含 的值LastError,所以我怀疑我可以利用这里的任何索引。

该表(未压缩时)包含大约 46 gigs 的数据,但大部分数据位于名为html. 我相信简单地加载和卸载这么多页面会导致速度变慢。一个想法是创建一个只有Id和字段的新表html,并保持Indexer.Pages尽可能小。然而,测试这个理论将是相当多的工作,因为我实际上没有硬盘空间来创建表的副本。我必须将它复制到另一台机器上,删除表,然后将数据复制回来,这可能需要整个晚上。

想法?我正在使用 Postgres 9.0.0。

更新:

这是架构:

CREATE TABLE indexer.pages
(
  id uuid NOT NULL,
  url character varying(1024) NOT NULL,
  firstcrawled timestamp with time zone NOT NULL,
  lastcrawled timestamp with time zone NOT NULL,
  recipeid uuid,
  html text NOT NULL,
  lasterror character varying(1024),
  missingings smallint,
  CONSTRAINT pages_pkey PRIMARY KEY (id ),
  CONSTRAINT indexer_pages_uniqueurl UNIQUE (url )
);

我也有两个索引:

CREATE INDEX idx_indexer_pages_missingings
  ON indexer.pages
  USING btree
  (missingings )
  WHERE missingings > 0;

CREATE INDEX idx_indexer_pages_null
  ON indexer.pages
  USING btree
  (recipeid )
  WHERE NULL::boolean;

此表上没有触发器,并且还有另一个表具有 FK 约束Pages.PageId

4

3 回答 3

6

@kgrittn作为评论发布的内容是迄今为止最好的答案。我只是在填写细节。

在你做任何其他事情之前,你应该将 PostgreSQL升级到当前版本,至少升级到你的主要版本的最后一个安全版本。请参阅项目指南。

我还想强调一下 Kevin 提到的关于该列的索引LastError。通常,HOT 更新可以回收数据页上的死行并使更新更快 - 有效地消除(大部分)对清理的需要。有关的:

如果您的列以任何方式在任何索引中使用,则 HOT UPDATE 将被禁用,因为它会破坏索引。如果是这种情况,您应该能够通过在您之前删除所有这些索引并稍后重新创建它们来大大加快查询速度UPDATE

在这种情况下,运行多个较小的 UPDATE 会有所帮助: 如果...
... 更新的列不涉及任何索引(启用 HOT 更新)。...在多个事务UPDATE中很容易分为多个补丁。...这些补丁中的行分布在表格上(物理上,而不是逻辑上)。...没有其他并发事务阻止死元组被重用。

然后你不需要VACCUUM在多个补丁之间进行,因为 HOT 更新可以直接重用死元组 - 仅来自先前事务的死元组,而不是来自相同或并发事务的死元组。您可能想VACUUM在操作结束时安排 a ,或者让自动吸尘完成它的工作。

可以对不需要的任何其他索引进行相同的操作UPDATE- 并且从您的数字来看,UPDATE无论如何都不会使用索引。如果您更新表的大部分内容,从头开始构建新索引比使用每个更改的行增量更新索引要快得多。

此外,您的更新不太可能破坏任何外键约束。您也可以尝试删除并重新创建它们。这确实打开了一个不会强制执行参照完整性的时隙。UPDATE如果在尝试重新创建 FK 时违反了完整性,则会出现错误。如果您在一个事务中完成所有操作,并发事务永远不会看到删除的 FK,但您会在表上获得写锁定 - 与删除/重新创建索引或触发器相同)

最后,禁用和启用更新不需要的触发器。

确保在一次交易中完成所有这些。也许在一些较小的补丁中进行,所以它不会阻塞并发操作太久。

所以:

BEGIN;
ALTER TABLE tbl DISABLE TRIGGER user; -- disable all self-made triggers
-- DROP indexes (& fk constraints ?)
-- UPDATE ...
-- RECREATE indexes (& fk constraints ?)
ALTER TABLE tbl ENABLE TRIGGER user;
COMMIT;

您不能VACUUM在事务块内运行。根据文档:

VACUUM不能在事务块内执行。

您可以将您的操作分成几个大块并在其间运行:

VACUUM ANALYZE tbl;

如果您不必处理并发事务,则可以(甚至更有效):

ALTER TABLE tbl DISABLE TRIGGER user; -- disable all self-made triggers
-- DROP indexes (& fk constraints ?)

-- Multiple UPDATEs with logical slices of the table
-- each slice in its own transaction.
-- VACUUM ANALYZE tbl;  -- optionally in between, or autovacuum kicks in

-- RECREATE indexes (& fk constraints ?)
ALTER TABLE tbl ENABLE TRIGGER user;
于 2012-06-18T18:19:28.687 回答
1
UPDATE Indexer.Pages 
  SET LastError=NULL
  ;

不需要 where 子句,因为 NULL 字段已经是 NULL,因此再次将它们设置为 NULL 不会有害(我认为这不会显着影响性能)。

鉴于您的 number_of_rows = 500K 并且您的表大小 = 46G,我得出的结论是您的平均行大小为 90KB。那是巨大的。也许您可以将表的 {unused, sparse} 列移动到其他表?

于 2012-06-18T16:50:22.830 回答
0

你的理论可能是正确的。阅读完整的表格(然后做任何事情)可能会导致速度变慢。

为什么不创建另一个具有 PageId 和 LastError 的表?使用您现在拥有的表中的数据对其进行初始化(这应该花费不到 93 分钟)。然后,使用新表中的 LastError。

闲暇时,您可以从现有表中删除 LastError。

顺便说一句,我通常不建议将列的两个副本保存在两个单独的表中。但是,在这种情况下,您听起来像是被卡住了,需要一种方法来继续。

于 2012-06-18T16:15:10.640 回答