有效地更新非常大的PostgreSQL数据库表

时间:2008-09-21 21:35:53

标签: sql database postgresql sql-update mvcc

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

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

更新表集已复制= 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顺序浏览表...

(请注意,在这种情况下,还有一个涵盖此内容的索引)

(PostgresQL无法使用像Mysql这样的更新LIMIT)

BTW:真正的问题更复杂,我们在谈论已部署的嵌入式系统,因此远程架构更改很困难,但可能 不幸的是,它是PostgresQL 7.4。

我正在谈论的行数是例如90000000.数据库的大小可以是几个千兆字节。

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

有什么想法吗?

6 个答案:

答案 0 :(得分:9)

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

当你想要将它们全部设置为0时,这也会简化这种情况,因为你可以截断表(它将磁盘上的表大小归零,你甚至不需要真空来释放空间)

答案 1 :(得分:3)

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

修改:为了避免在复制14千兆字节时锁定表格,这是一项简单的改进:

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;

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

答案 2 :(得分:2)

虽然您无法解决空间使用问题(这是暂时的,直到真空),但您可能真的可以在时钟时间方面加快进程。 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;

答案 3 :(得分:1)

这是伪代码。你需要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);

答案 4 :(得分:1)

我认为最好将你的postgres更改为8.X版。可能原因是Postgres的低版本。也请尝试以下查询。我希望这可以提供帮助。

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

答案 5 :(得分:0)

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