优化 PostgreSQL 中的批量更新性能

Yan*_*ang 47 postgresql performance bulk update

在 Ubuntu 12.04 上使用 PG 9.1。

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

UPDATE table
SET field1 = constant1, field2 = constant2, ...
WHERE id = constid
Run Code Online (Sandbox Code Playgroud)

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

每个表都有几个索引,没有外键约束。直到最后都没有提交。

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

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

一些想法:

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

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

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

Erw*_*ter 58

假设

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

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

解决方案

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

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

CREATE TEMP TABLE tmp_tbl AS SELECT * FROM tbl LIMIT 0;
Run Code Online (Sandbox Code Playgroud)

UPDATE来自数据库内部临时表的单个 big将比来自数据库外部的单个更新快几个数量级。

PostgreSQL 的 MVCC 模型中,一种UPDATE创建新行版本并将旧版本标记为已删除的方法。这大约INSERT与 a 和 aDELETE组合一样昂贵。另外,它会给你留下很多死元组。由于无论如何您都在更新整个表,因此只需创建一个新表并删除旧表总体上会更快。

如果您有足够的可用 RAM,请设置temp_buffers(仅针对此会话!)足够高以将临时表保存在 RAM 中 - 在您执行任何其他操作之前。

要估计需要多少 RAM,请使用小样本运行测试并使用db 对象大小函数

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
Run Code Online (Sandbox Code Playgroud)

完整脚本

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
Run Code Online (Sandbox Code Playgroud)

并发负载

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

更新路线

如果您(必须)走这UPDATE条路线,请删除更新期间不需要的任何索引,然后重新创建它。一次性创建索引比为每一行更新索引要便宜得多。这也可以允许热更新

UPDATE关于 SO 的这个密切相关的答案中概述了一个类似的过程。

 

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