在PostgreSQL中更新非常大的表而不锁定

在PostgreSQL中更新非常大的表而不锁定,postgresql,sql-update,large-data,Postgresql,Sql Update,Large Data,我有一个非常大的表,有100M行,我想在另一列的基础上用一个值更新一列。下面给出了显示我想做什么的示例查询: UPDATE mytable SET col2 = 'ABCD' WHERE col1 is not null 这是一个主数据库,在一个有多个从属服务器的实时环境中,我希望在不锁定表或影响实时环境性能的情况下对其进行更新。最有效的方法是什么?我正在考虑做一个程序,使用limit之类的东西成批更新1000行或10000行,但不太确定如何做,因为我对Postgres及其陷阱不太熟悉。哦,这

我有一个非常大的表,有100M行,我想在另一列的基础上用一个值更新一列。下面给出了显示我想做什么的示例查询:

UPDATE mytable SET col2 = 'ABCD'
WHERE col1 is not null
这是一个主数据库,在一个有多个从属服务器的实时环境中,我希望在不锁定表或影响实时环境性能的情况下对其进行更新。最有效的方法是什么?我正在考虑做一个程序,使用limit之类的东西成批更新1000行或10000行,但不太确定如何做,因为我对Postgres及其陷阱不太熟悉。哦,这两个列都没有任何索引,但表中有其他具有索引的列

我希望有一个示例程序代码


谢谢。

没有锁定就没有更新,但您可以努力保持行锁定少且短

您只需运行以下批处理:

UPDATE mytable
SET col2 = 'ABCD'
FROM (SELECT id
      FROM mytable
      WHERE col1 IS NOT NULL
        AND col2 IS DISTINCT FROM 'ABCD'
      LIMIT 10000) AS part
WHERE mytable.id = part.id;
只要不断重复该语句,直到它修改的行数少于10000行,就完成了

请注意,批量更新不会锁定表,但它们当然会锁定更新的行,更新的行越多,事务越长,死锁的风险越大

要实现这一性能,这样的索引将有助于:

CREATE INDEX ON mytable (col2) WHERE col1 IS NOT NULL;

只是一个现成的想法。col1和col2都必须为null才能限定使用索引的可能性,也许可以选择构建psudo索引。这个索引当然是一个常规表,但只存在很短的时间。此外,这还减轻了锁定时间的担忧

create table indexer (mytable_id integer  primary key);

insert into indexer(mytable_id)
select mytable_id
  from mytable
 where col1 is null
   and col2 is null;
上面创建的“索引”只包含符合条件的行。现在将update/delete语句包装到SQL函数中。此函数用于更新主表并从“索引”中删除更新的行,并返回剩余的行数

create or replace function set_mytable_col2(rows_to_process_in integer)
returns bigint
language sql
as $$
    with idx as
       ( update mytable
            set col2 = 'ABCD'
          where col2 is null
            and mytable_id in (select mytable_if 
                                 from indexer
                                limit rows_to_process_in
                               )
         returning mytable_id
       )
    delete from indexer
     where mytable_id in (select mytable_id from idx);

    select count(*) from indexer;
$$; 
当函数返回0时,所有最初选择的行都已被处理。此时,重复整个过程以拾取初始选择未识别的任何添加或更新的行。应为小数量,且流程仍可用,需要稍后进行。 就像我说的,只是一个非常规的想法

编辑 一定是读到了col1没有的东西。不过,想法仍然是一样的,只需更改“indexer”的INSERT语句即可满足您的需求。至于在“索引”中设置它,则“索引”包含单个列—大表及其本身的主键。 是的,除非将要处理的行总数作为参数,否则需要多次运行。下面是一个DO块,可以满足您的条件。它每通过一次就处理200000。改变它以适应你的需要

Do $$
declare 
    rows_remaining bigint;
begin    
loop
    rows_remaining = set_mytable_col2(200000);
    commit;
    exit when rows_remaining = 0;
end loop;
end; $$; 

col2最初是什么?其他流程是否也在同时更新它?它真的是“ABC”,还是特定于行的?您担心表级锁还是行级锁?您是否已经尝试过这个简单的解决方案,但发现它站不住脚,或者您正在先发制人地担心呢?您是否意识到,即使该更新锁定了许多行而不是表!这些锁不会阻止该表上的SELECT或INSERT语句。我还没有尝试过它,我担心会先发制人,因为它对应用程序的功能非常重要。col2为null,我应该只在它为null时设置它。其他进程不会更新它,只会插入一行,其中包含一些值。@DashingBoy:那么您就没有问题了。更新会在系统上增加一些I/O负载,但锁不会打扰您,应该是这样的,col2与更新中的“ABCD”不同。我想我也应该把它添加到索引WHERE中。在WHERE子句中,只有当常量为-well-constant时才是好的。有一件事我不明白,这个查询就足够了吗?没有循环?我应该用循环将其封装在一个过程中吗?我想我没有完全理解DISTINCE from子句。我添加了用法说明以使其更清楚。col2与'ABCD'不同,它与col2='ABCD'相反,因此没有行被更新两次。我想我没有详细说明。如果在循环中运行批次,如何确保批次完整。我该如何编写退出条件,以及它是在单个事务中,还是应该在不同的事务中拆分每个批。很抱歉,如果这一切都很明显,我没有领会。我需要反复运行函数,对吗?是否有一种方法可以在一个函数调用中使用循环多次提交,这样就不必再次手动运行它?根据我的条件,col1也不是null,我假设您将它设置为null,因为索引表是一个类型?