postgresql 如何使用postgres查询一次并行更新多行?

tvokkenx  于 11个月前  发布在  PostgreSQL
关注(0)|答案(2)|浏览(191)

我有下面的postgres查询,这意味着更新所有的消息条目:
第一个月
麻烦的是,我有大约300,000,000条消息记录,而postgres似乎只使用一个核心来进行这个大规模的更新,导致操作完成之前IO超时。
如何加快这个简单的更新命令,使其尽可能快?
第一个子句中的类型是UUID,第二个子句中的类型是i64。

58wvjzkj

58wvjzkj1#

仅限SELECT的并行查询计划

Postgres只对SELECT使用并行计划(以及一些基于SELECT的命令,如CREATE TABLE AS)。无法并行修改任何行。The manual:
即使通常可以生成并行查询计划,如果满足以下任一条件,计划程序也不会为给定查询生成并行查询计划:

  • 查询写入任何数据或锁定任何数据库行。如果查询在顶层或CTE中包含数据修改操作,则不会为该查询生成并行计划。

手动并行

回答你的问题:沿着沿着user_pgid将表分割成 N 个不重叠的切片,切片大小大致相等-其中 N 是并行使用的进程数。不应超过可用的CPU核心数。总的来说,保持低于DB服务器的I/O容量。
创建一个PROCEDURE,如下所示:

CREATE OR REPLACE PROCEDURE public.f_upd_message(
     _lower_incl bigint
   , _upper_excl bigint
   , _step int = 50000
   )
  LANGUAGE plpgsql AS
$proc$
DECLARE
   _low bigint;
   _upd_ct int;
BEGIN
   IF _upper_excl <= _lower_incl OR _step < 1 THEN
      RAISE EXCEPTION '_upper_excl must be > _lower_inc & _step > 0! Was: _lower_incl: %, _upper_excl: %, _step: %'
                     , _lower_incl, _upper_excl, _step;
   END IF;
   
   FOR _low IN _lower_incl .. _upper_excl - 1 BY _step
   LOOP
      RAISE NOTICE 'user_pgid >= % AND user_pgid < %'
                  , _low, LEAST(_upper_excl, _low + _step);  -- optional

      UPDATE public.message m
      SET    user_id = u.id
      FROM   public."user" u
      WHERE  m.user_pgid >= _low
      AND    m.user_pgid <  _low + _step
      AND    m.user_pgid <  _upper_excl  -- enforce upper bound
      AND    u.user_pgid = m.user_pgid
      AND    m.user_id <> u.id;          -- ① suppress empty updates
      
      GET DIAGNOSTICS upd_ct = ROW_COUNT;  -- optional

      COMMIT;

      RAISE NOTICE 'Updated % rows', upd_ct;  -- optional
   END LOOP;
END
$proc$;

字符串
电话:

CALL public.f_upd_message(20000000, 30000000);


或者:

CALL public.f_upd_message(20000000, 30000000, 100000);


①避免空更新(列值不会改变)。如果user_id可以为null,请使用与IS DISTINCT FROM的空安全比较。
还可以防止重复更新,以防您必须重新开始或弄乱切片。请参阅:

  • 我如何(或者我可以)在多个列上选择DISTINCT?

将切片基于 * 实际 * 最小和最大user_pgid

SELECT min(user_pgid) AS _lower_incl, max(user_pgid) + 1 AS _upper_excl
FROM   public.message;


根据你的系统调整_step的大小。我添加了默认值50000。
然后运行 N 个单独的会话,每个会话处理一个切片。比如,启动 N 个psql示例(手动或在shell脚本中)。
每一步都是提交的。如果你仍然遇到超时(或任何其他问题),提交的工作不会回滚。
相关:

  • 在PostgreSQL中,过程是否在事务中运行?
  • 如何在函数内部强制提交,以便其他会话可以看到更新的行?

副作用、注意事项

由于每次更新都会留下一个死元组,因此表的大小将增加两倍。如果你有能力,你可能想在之后运行VACUUM FULL。或者,在过程中以合理的间隔发出VACUUM,以从死元组中腾出空间供重用。请参阅:

其他各种优化都是可能的。比如删除并稍后重新创建FK约束,索引,.但是你绝对需要在message(user_pgid)上**索引!

如果你可以这么做,那么创建一个更新的(排序的?)表副本,而不是更新所有行。就像Frank已经建议的那样。这会给你一个没有膨胀的原始(集群)表。

gcuhipw9

gcuhipw92#

要加快对大型表的更新操作,可以尝试一些技术来优化查询:

批量处理您可以在较小的批中更新记录,而不是在单个事务中更新所有记录。这可以减少对系统资源的影响,并有助于避免超时。

批处理的示例实现:

DO $$ 
DECLARE 
   batch_size integer := 30000; -- Adjust the batch size as needed
BEGIN
   FOR i IN 0..(SELECT ceil(count(*)::numeric / batch_size) FROM message) LOOP
      UPDATE message m 
      SET user_id = u.id 
      FROM "user" u 
      WHERE u.user_pgid = m.user_pgid 
      LIMIT batch_size 
      OFFSET i * batch_size;
   END LOOP;
END $$;

字符串

相关问题