在PostgreSQL中优化批量更新性能


37

在Ubuntu 12.04上使用PG 9.1。

目前,我们最多需要24小时才能在数据库上运行大量的UPDATE语句,其形式为:

UPDATE table
SET field1 = constant1, field2 = constant2, ...
WHERE id = constid

(我们只是覆盖由ID标识的对象的字段。)这些值来自外部数据源(尚未存在于表的DB中)。

该表每个都有少量索引,没有外键约束。直到结束都不会进行COMMIT。

导入pg_dump整个数据库需要2小时。这似乎是我们应该合理定位的基准。

缺少生成自定义程序以某种方式为PostgreSQL重新构造数据集以重新导入的方法,我们是否可以做些什么使批量UPDATE性能更接近于导入?(这是我们认为日志结构的合并树可以很好处理的一个区域,但是我们想知道PostgreSQL中是否可以做任何事情。)

一些想法:

  • 删除所有非ID索引并随后进行重建?
  • 增加checkpoint_segments,但这实际上有助于持续的长期吞吐量吗?
  • 使用这里提到的技术?(将新数据作为表加载,然后“合并”在新数据中找不到ID的旧数据)

基本上,有很多事情可以尝试,我们不确定最有效的是什么,或者我们是否忽略了其他事情。我们将在接下来的几天中进行实验,但我们也想在这里提出问题。

我确实在表上有并发负载,但是它是只读的。


您的问题中缺少重要信息:您的Postgres版本?价值从何而来?听起来像数据库外的文件,但请澄清一下。您在目标表上有并发负载吗?如果是,那到底是什么?还是您可以负担得起并重新创建?没有外键,好的-但是还有其他依赖对象,例如视图吗?请使用缺少的信息编辑您的问题。不要在评论中挤压它。
Erwin Brandstetter

@ErwinBrandstetter谢谢,更新了我的问题。

我假设您已经检查explain analyze过它是否使用索引进行查找?
rogerdpack

Answers:


45

假设条件

由于Q中缺少信息,因此我假设:

  • 您的数据来自数据库服务器上的文件。
  • 数据的格式与COPY输出一样,每行唯一 id,以匹配目标表。
    如果不是,请先对其进行正确格式化,或者使用COPY选项进行格式化。
  • 您正在更新目标表中的每一行或其中的大多数。
  • 您有能力删除并重新创建目标表。
    这意味着没有并发访问。否则请考虑以下相关答案:
  • 除了索引外,根本没有依赖对象。

我建议您采用第三个项目符号链接中概述的类似方法。进行重大优化。

要创建临时表,有一种更简单,更快捷的方法:

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

数据库内部UPDATE临时表中的单个大数据块比数据库外部中的单个更新大几个数量级。

PostgreSQL的MVCC模型中,一种UPDATE创建新行版本并将旧版本标记为已删除的方法。这INSERT和和一样贵DELETE。此外,它还会留下很多死元组。由于您还是要更新整个表,因此创建一个新表并删除旧表会更快。

如果您有足够的可用temp_buffersRAM,则在执行其他任何操作之前,请将其高(仅用于该会话!)设置为足以将临时表保留在RAM中。

要估算需要多少RAM,请使用一个小样本运行测试并使用db object size函数

SELECT pg_size_pretty(pg_relation_size('tmp_tbl'));  -- complete size of table
SELECT pg_column_size(t) FROM tmp_tbl t LIMIT 10;  -- size of sample rows

完整的脚本

SET temp_buffers = '1GB';        -- example value

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;

COPY tmp_tbl FROM '/absolute/path/to/file';

CREATE TABLE tbl_new AS
SELECT t.col1, t.col2, u.field1, u.field2
FROM   tbl     t
JOIN   tmp_tbl u USING (id);

-- Create indexes like in original table
ALTER TABLE tbl_new ADD PRIMARY KEY ...;
CREATE INDEX ... ON tbl_new (...);
CREATE INDEX ... ON tbl_new (...);

-- exclusive lock on tbl for a very brief time window!
DROP TABLE tbl;
ALTER TABLE tbl_new RENAME TO tbl;

DROP TABLE tmp_tbl; -- will also be dropped at end of session automatically

并发负载

一旦将表锁定在末尾附近,对表的并发操作(我在开始时的假设中已排除)将等待,并在提交事务后立即失败,因为表名会立即解析为其OID,但是新表具有不同的OID。该表保持一致,但是并发操作可能会出现异常,因此必须重复执行。相关答案的详细信息:

更新路线

如果(必须)走这UPDATE条路线,请删除更新期间不需要的所有索引,然后再重新创建。在一个索引中创建索引要比为每一行更新索引便宜得多。这也可能允许HOT更新

我使用提出了一个类似的过程UPDATE对SO此密切相关的答案

 


1
我实际上只是在更新目标表中的20%的行-不是全部,而是足够大的一部分以至于合并可能比随机更新要好。

1
@AryehLeibTaurog:这不应该发生,因为DROP TABLE取出了Access Exclusive Lock。无论哪种方式,我都已经在答案的顶部列出了先决条件:You can afford to drop and recreate the target table.在事务开始时锁定表可能会有所帮助。我建议您开始一个新的问题,其中包含您所处情况的所有相关详细信息,以便我们深入探讨这一问题。
Erwin Brandstetter 2014年

1
@ErwinBrandstetter有趣。它似乎取决于服务器版本。我已经使用psycopg2适配器psql客户端重现了8.4和9.1上的错误。在9.3上没有错误。在第一个脚本中查看我的评论。我不确定是否要在此处发布问题,但是值得在其中一个postgresql列表上征求一些信息。
Aryeh Leib Taurog 2014年

1
我在python中编写了一个简单的帮助程序类,以自动执行该过程。
Aryeh Leib Taurog 2014年

3
非常有用的答案。作为一种稍微的变化,可以创建仅包含要更新的列和引用列的临时表,从原始表中删除要更新的列,然后使用合并表CREATE TABLE tbl_new AS SELECT t.*, u.field1, u.field2 from tbl t NATURAL LEFT JOIN tmp_tbl u;LEFT JOIN从而允许保留没有更新的行。当然NATURAL可以将其更改为任何有效USING()ON
Skippy le Grand Gourou

2

如果可以在结构化文件中提供数据,则可以使用外部数据包装器读取数据并在目标表上执行合并。


3
“合并到目标表”的具体含义是什么?为什么使用FDW比将COPYing到临时表中更好(如原始问题的第三个项目符号所建议)?

如MERGE sql语句中的“合并”。使用FDW,您可以执行此操作而无需执行将数据复制到临时表中的附加步骤。我假设您不是要替换整个数据集,并且文件中会有一定数量的数据,这些数据不代表对当前数据集的更改-如果发生了很大的变化,那么将更改整个数据集。替换表可能是值得的。
David Aldridge

1
@DavidAldridge:虽然在SQL:2003标准中定义,但尚未MERGE在PostgreSQL中实现。其他RDBMS中的实现相差很大。考虑标签信息MERGEUPSERT
Erwin Brandstetter,

@ErwinBrandstetter [笑]哦,是的。好吧,合并真的是锦上添花。无需导入临时表步骤即可访问数据确实是FDW技术的症结所在。
大卫·奥尔德里奇
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.