PostgreSQL中有效批量更新的布尔字段

时间:2014-05-21 17:56:44

标签: postgresql

是否有一种特别有效的方法来批量更新Postgres中的大型或记录的简单布尔字段?

我有一个包含数百万行的表,有时我想在这些行的大型但索引良好的子集上标记fresh=false

但是,如果我尝试做显而易见的事情:

UPDATE mytable SET fresh=false WHERE mycriteria;

它运行了几个小时,消耗所有内存,开始交换,使我的机器几乎无法使用,迫使我终止进程,导致数据库中的任何数据都没有变化。

相反,我已经编写了一个bash脚本来一次运行几千条记录的迷你块中的这个更新,这仍然需要几个小时,但至少可以完成工作,并为我提供进度信息。还有更好的方法吗?

1 个答案:

答案 0 :(得分:2)

  

它运行了几个小时,消耗所有内存,开始交换,使我的机器几乎无法使用,迫使我终止进程,导致数据库中的任何数据都没有变化。

根据您可能已定义AFTER UPDATE ... FOR EACH ROW个触发器的说明。

目前,PostgreSQL(在9.4和之前的情况下为真)至少使用内存中队列作为触发器。它是一个高效的队列,但它仍然在内存中,并且在几百万行开始真正加起来之后。

要确认是这种情况,您应该gdbpostgres进程附加到gdb -p the-big-postgres-process-id进程,并使用gdb -p 1234进行工作。 1234 postgres topSELECT pg_backend_pid()的pid,显示为使用UPDATE中的大量内存。或者您可以gdb运行(gdb)

无论哪种方式,一旦你有(gdb) p MemoryContextStats(TopMemoryContext) (gdb) detach (gdb) quit 附加,并且你在gdb提示符运行:

AFTER UPDATE ... FOR EACH ROW

如果FOR EACH STATEMENT抱怨缺少符号,则可能必须先安装debuginfo包;见the instructions on the wiki

这将确认内存的实际位置。

如果这确实是NEW触发器,那么您的选项是:

  • 请改用OLD触发器。在这种情况下,无法获得BEFOREAFTER

  • 使用{{1}}触发器;或

  • 赞助开发{{1}}触发器队列的溢出到磁盘存储; - )

顺便说一句,要记住的一件事是,如果你有一个100列宽的表并且你更新了一个字段,仍然必须复制每一列并将其写入新的行拷贝,因为MVCC。例外是存储在线外的TOASTable列(非平凡的文本字段,数组,字节字段等);如果没有修改,则不必复制。因此,“微不足道”的更新可能并不像您想象的那么简单。