高效更新 VERY LARGE PostgreSQL 数据库表

Posted

技术标签:

【中文标题】高效更新 VERY LARGE PostgreSQL 数据库表【英文标题】:Update VERY LARGE PostgreSQL database table efficiently 【发布时间】:2010-09-11 20:27:58 【问题描述】:

我在 PostgresQL 中有一个非常大的数据库表和一个类似“已复制”的列。每一个新行开始时都是未复制的,稍后将被后台程序复制到另一个东西上。该表“btree(ID) WHERE replicated=0”上有一个部分索引。后台程序最多选择 2000 个条目(LIMIT 2000),对它们进行处理,然后使用 2000 个准备好的 sql 命令在一个事务中提交更改。

现在的问题是我想给用户一个选项来重置这个复制值,让它再次全部为零。

一个更新表集replicated=0;

不可能:

需要很长时间 由于 MVCC,它复制了表格的大小 在一个事务中完成:要么失败,要么通过。

在这种情况下,我实际上不需要事务功能:如果系统出现故障,它将只处理其中的一部分。

其他几个问题: 做一个

update set replicated=0 where id >10000 and id<20000

也很糟糕:它对整个表进行顺序扫描,这太慢了。 如果不这样做,它仍然会很慢,因为它会进行太多的搜索。

我真正需要的是一种遍历所有行、更改它们而不被绑定到巨大事务的方法。

奇怪的是

UPDATE table 
  SET replicated=0 
WHERE ID in (SELECT id from table WHERE replicated= LIMIT 10000)

也很慢,虽然这应该是件好事:按 DISK-order 遍历表...

(请注意,在这种情况下,还有一个索引涵盖了这一点)

mysql 之类的更新 LIMIT 不适用于 PostgresQL)

顺便说一句:真正的问题更复杂,我们在这里讨论的是已经部署的嵌入式系统,因此远程架构更改很困难,但可能 不幸的是,它是 PostgresQL 7.4。

我所说的行数是例如90000000。数据库的大小可以是几十G。

数据库本身只包含5张表,其中一张非常大。 但这不是一个糟糕的设计,因为这些嵌入式盒子只与一种实体一起操作,而不是 ERP 系统或类似的东西!

有什么想法吗?

【问题讨论】:

【参考方案1】:

如何添加一个新表来存储此复制值(以及将每条记录链接到主表的主键)。然后,您只需为每个复制项目添加一条记录,并删除记录以删除复制标志。 (或者可能反过来 - 每个非复制记录的记录,取决于常见情况)。

当您想将它们全部设置回 0 时,这也将简化情况,因为您可以截断表(这会将磁盘上的表大小归零,您甚至不必清理以释放空间)

【讨论】:

这是一个非常好的主意,尽管不幸的是它需要更改架构(一个冗长的更新过程)。我真正喜欢这种方法的地方在于,实际上当前的部分索引在内部与这个想法非常相似!只有更加灵活和易于管理。【参考方案2】:

如果您尝试重置整个表,而不仅仅是几行,通常更快(在非常大的数据集上——而不是在常规表上)简单地CREATE TABLE bar AS SELECT everything, but, copied, 0 FROM foo,然后交换表并删除旧的一。显然,您需要确保在执行此操作时没有将任何内容插入原始表中。您还需要重新创建该索引。

编辑:一个简单的改进,以避免在复制 14 GB 时锁定表:

lock ;
create a new table, bar;
swap tables so that all writes go to bar;
unlock;
create table baz as select from foo;
drop foo;
create the index on baz;
lock;
insert into baz from bar;
swap tables;
unlock;
drop bar;

(在您进行复制时让写入发生,并在事后插入它们)。

【讨论】:

【参考方案3】:

虽然您不可能解决空间使用问题(它是暂时的,直到真空),但您可能真的可以在时钟时间方面加快进程。 PostgreSQL 使用 MVCC 的事实意味着您应该能够做到这一点,而不会出现与新插入的行相关的任何问题。 create table as select 将解决一些性能问题,但不允许继续使用该表,并且占用同样多的空间。只需放弃索引,重新构建它,然后做一个真空。

drop index replication_flag;
update big_table set replicated=0;
create index replication_flag on big_table btree(ID) WHERE replicated=0;
vacuum full analyze big_table;

【讨论】:

【参考方案4】:

这是伪代码。您将需要 400MB(对于 int)或 800MB(对于 bigints)临时文件(如果有问题,您可以使用 zlib 对其进行压缩)。它需要对一张桌子进行大约 100 次扫描以进行真空吸尘器。但它不会使表膨胀超过 1%(任何时候最多 1000000 个死行)。您还可以用更少的扫描来换取更多的表膨胀。

// write all ids to temporary file in disk order                
// no where clause will ensure disk order
$file = tmpfile();
for $id, $replicated in query("select id, replicated from table") 
        if ( $replicated<>0 ) 
                write($file,&$id,sizeof($id));
        


// prepare an update query
query("prepare set_replicated_0(bigint) as
        update table set replicated=0 where id=?");

// reread this file, launch prepared query and every 1000000 updates commit
// and vacuum a table
rewind($file);
$counter = 0;
query("start transaction");
while read($file,&$id,sizeof($id)) 
        query("execute set_replicated_0($id)");
        $counter++;
        if ( $counter % 1000000 == 0 ) 
                query("commit");
                query("vacuum table");
                query("start transaction");
        

query("commit");
query("vacuum table");
close($file);

【讨论】:

【参考方案5】:

我猜你需要做的是 一种。将 2000 条记录的 PK 值复制到具有相同标准限制的临时表中,等等。 湾。选择相同的 2000 条记录并按原样在游标中执行必要的操作。 C。如果成功,请针对临时表中的记录运行单个更新查询。清除临时表并再次运行步骤 a。 d。如果不成功,请清除临时表而不运行更新查询。 简单、高效、可靠。 问候, 韩特

【讨论】:

【参考方案6】:

我认为最好将您的 postgres 更改为 8.X 版本。可能原因是 Postgres 的低版本。也试试下面的这个查询。我希望这会有所帮助。

UPDATE table1 SET name = table2.value
FROM table2 
WHERE table1.id = table2.id;

【讨论】:

以上是关于高效更新 VERY LARGE PostgreSQL 数据库表的主要内容,如果未能解决你的问题,请参考以下文章

:Introduction to Very Large Databases 读书笔记

:Introduction to Very Large Databases 读书笔记

从 url 列表中下载 <very large> 页面的最佳方法是啥?

VGGnet论文总结(VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION)

VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION(VGG网络)-论文阅读笔记

了解 ETL 流程