在不锁定的情况下更新 PostgreSQL 中非常大的 table

Update a very large table in PostgreSQL without locking

我有一个非常大的 table,有 100M 行,我想在其中用基于另一列的值更新一列。下面给出了显示我想要执行的操作的示例查询:

UPDATE mytable SET col2 = 'ABCD'
WHERE col1 is not null

这是具有多个从属的实时环境中的主数据库,我想在不锁定 table 或影响实时环境性能的情况下更新它。最有效的方法是什么?我正在考虑制作一个程序,使用 limit 之类的东西分批更新 1000 或 10000 行的行,但不太确定该怎么做,因为我不太熟悉 Postgres 及其陷阱。哦,两列都没有任何索引,但 table 有其他列。

我希望能提供示例过程代码。

谢谢。

没有锁就没有更新,但是可以尽量保持行锁少且短。

你可以简单地运行批这样的:

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 行,就大功告成了。

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

为了提高性能,像这样的索引会有所帮助:

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

只是一个现成的、开箱即用的想法。 col1 和 col2 都必须为 null 才能排除使用索引的资格,也许构建 psudo 索引可能是一种选择。这个索引当然是一个常规的 table 但只会存在很短的时间。此外,这减轻了锁定时间的担忧。

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;

上面创建的 'index' 只包含符合条件的行。现在将 update/delete 语句包装到 SQL 函数中。此函数更新主要 table 并从 'index' 和 returns 剩余行数中删除更新的行。

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;
$$; 

当函数 returns 0 时,所有最初选择的行都已处理。此时重复整个过程以拾取初始选择未识别的任何添加或更新的行。应该是少量的,以后需要的工艺还是可用的。
就像我说的只是一个现成的想法。

已编辑 一定是读到了一些关于 col1 不存在的东西。然而,想法保持不变,只需更改 'indexer' 的 INSERT 语句以满足您的要求。至于在 'index' 中设置它,'index' 不包含单个列 - 大 table(及其本身)的主键。
是的,您需要 运行 多次,除非您将要处理的总行数作为参数提供给它。以下是满足您条件的 DO 块。它每次处理 200,000 个。更改它以满足您的需要。

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