如何加快缓慢的UPDATE查询

时间:2012-06-18 16:08:56

标签: sql postgresql

我有以下UPDATE查询:

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

现在,此查询大约需要93分钟才能完成。我想找到方法让它更快一点。

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

该表(未压缩时)中包含大约46个数据,但大部分数据位于名为html的文本字段中。我相信只需加载和卸载那么多页面就会导致速度减慢。一个想法是使用 Idhtml字段创建一个新表,并尽可能减少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;

此表上没有触发器,还有一个表在Pages.PageId上有一个FK约束。

3 个答案:

答案 0 :(得分:6)

到目前为止,@kgrittn posted as comment是最佳答案。我只是在填写细节。

在您执行任何其他操作之前,应该 upgrade PostgreSQL to a current version,至少是主要版本的最后一个安全版本。 See guidelines on the project.

我还想强调Kevin提到的涉及列LastError索引。通常,热更新可以回收数据页面上的死行,并使更新速度更快 - 有效地消除(大部分)需要进行抽真空。相关:

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

在此上下文中,它将有助于运行多个较小的UPDATE: 如果 ...
 ...更新的列不涉及任何索引(启用HOT更新)。  ... UPDATE很容易在多个交易中分成多个补丁。  ...这些补丁中的行分布在表格上(物理上,而不是逻辑上)。  ...没有其他并发事务可以防止重复使用死元组。

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

对于UPDATE不需要的任何其他索引也可以这样做 - 并且根据您的数字判断UPDATE无论如何都不会使用索引。如果更新表的大部分内容,从头开始构建新索引要比使用每个更改的行逐步更新索引要快得多。

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

最后,更新不需要disable & enable triggers

请务必在一次交易中完成所有这些操作。也许在许多较小的补丁中进行,因此它不会阻止并发操作太长时间。

所以:

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;

您无法在事务块中运行VACUUMPer documentation:

  

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;

答案 1 :(得分:1)

UPDATE Indexer.Pages 
  SET LastError=NULL
  ;

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

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

答案 2 :(得分:0)

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

为什么不创建另一个具有PageId和LastError的表?使用您现在的表中的数据初始化它(这应该少于93分钟)。然后,使用新表中的LastError。

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

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