如何在不锁定 table 的情况下回填 Postgres 中的列?

How to backfill column in Postgres without locking the table?

我最近在我的 40+ 百万行 Postgres table (v9.6) 中添加了一个新专栏

ALTER TABLE queries
   ADD COLUMN ml_partition_source UUID;

然后在我执行的另一笔交易中

ALTER TABLE queries
   ALTER COLUMN ml_partition_source
      SET DEFAULT public.gen_random_uuid();

我在两个事务中完成了此操作,因为在新列上设置 default 会导致 Postgres 重写整个 table,这可能需要数小时并且无法接受 table生产中。

现在,我想在不锁定 table 的情况下为添加新列之前存在的所有 query 回填此列。一种方法是通过 CRUD API 我有,但一些粗略的计算表明这将需要大约 22 天(也许我的 API 性能可以提高,但这是一个完全不同的问题)。相反,我尝试编写一个 postgres 函数:

CREATE OR REPLACE FUNCTION backfill_partition_source()
  RETURNS void AS $$
declare
  query_ record;
BEGIN
  for query_ in
  select * from api_mldata.queries where ml_partition_source is null
  loop
    update api_mldata.queries SET ml_partition_source = public.gen_random_uuid() where id = query_.id;
  end loop;
END;
$$ LANGUAGE plpgsql;

并用 select backfill_partition_source(); 执行。但这最终也锁定了 table。

如何在不影响生产(或对生产影响最小)的情况下回填列?

编辑:我的一个想法是 "chunking" Postgres 脚本一次操作 100k 行或类似的东西,然后循环执行脚本。所以 select 语句将变成

select * from api_mldata.queries
where ml_partition_source is null
limit 100000;

不上锁根本无法逃脱,但可以将锁保持在适当的短。

而不是 运行在循环中进行许多单行更新,运行 更大的更新:

UPDATE api_mldata.queries
SET ml_partition_source = DEFAULT
WHERE id BETWEEN 1 AND 999999;

这里id是table的主键。

这样您就可以完成一些更大的更新,每个更新都针对不同的 id 范围。

为了避免膨胀和过度锁定,运行 每个语句在其自己的事务中并在语句之间的 table 上启动显式 VACUUM