Postgres函数优化

Postgres function optimisation

我是运行下面的函数。用小得多的 table 测试它按预期工作(18 行 - ~400 毫秒)。然而,当指向我的真实数据(315000 行)时,它是 运行 48 小时并且还在继续。这比我在线性外推下的预期要长得多。

  1. 有更好的方法吗?
  2. 有没有办法在仍然 运行 的情况下测试它是否正在做它应该做的事情?

有什么方法可以优化下面的功能吗?

DO 
$do$ 
DECLARE r public.tablex%rowtype; 
BEGIN 
FOR r IN SELECT id FROM public.tablex 
LOOP 
IF (select cast((select trunc(random() * 6 + 1)) as integer) = 5) THEN 
UPDATE public.tablex SET test='variable1' WHERE id = r.id; 
ELSIF (select cast((select trunc(random() * 6 + 1)) as integer) = 6) THEN 
UPDATE public.tablex SET test='variable2' WHERE id = r.id; 
END IF; 
END LOOP; 
RETURN; 
END 
$do$;

尝试简单的更新:

UPDATE tablex
SET  test =  CASE trunc(random() * 6) + 1
                 WHEN  5 THEN 'variable1'
                 WHEN  6 THEN 'variable2'
                 ELSE test
       END
;

估计至少要快50~200倍

明确表示您的 DO 声明不必要地昂贵。但还有更多。

概率

你的更新概率分布不均:

  • 1/6 或所有行更新为 'variable1'
  • 但只有5/36((1/6) * (5/6))更新为'variable2'

从你的代码的其余部分来看,我假设这是一个意外错误,你想要每个相等的 1/6 份额.

消毒

您可以简化为:

UPDATE tablex
SET    test = CASE trunc(random() * 6)
                 WHEN  float '4' THEN 'variable1'
                 WHEN  float '5' THEN 'variable2'
                 ELSE  test
              END;
  • 结果加1没有意义。而是比较 45 而不是 56(或 31 - 这里没有区别)。

  • 比较 double precision (= float) 常量而不是将表达式 trunc(random() * 6)double precision 结果转换为integer 每行,就像在您的原始代码中一样。

好多了,但是仍然很低效

高级

UPDATE tablex
SET    test = CASE WHEN random() >= float '0.5' THEN 'variable1'
                                                ELSE 'variable2' END
WHERE  random() >= float '0.6666667';

100 % 等效(除非你有触发器 ON UPDATE 或一些奇特的设置)但 快得多 ,因为只有实际接收更新的行才会被触及。三分之二的行根本没有被触及。

  • 请注意,这两个 random() 调用是完全独立的。

  • 如果您想尽可能精确地了解三分之二的概率,请使用 2 / float '3.0' 而不是 float '0.6666667'。但这确实是学术上的差异。

  • 您可能希望 LOCK tablex IN ROW EXCLUSIVE MODE 在 运行 之前(在同一事务中)排除并发写入的竞争条件。 Details in the manual.

备选方案

如果test 已经可以包含目标值之一 'variable1' 或 'variable2' (在很多情况下)这个更便宜,但是:

UPDATE tablex t
SET    test = upd.val
FROM  (
   SELECT id, CASE WHEN random() >= float '0.5' THEN 'variable1'
                                                ELSE 'variable2' END AS val
   FROM   tablex
   WHERE  random() >= float '0.6666667'
   -- ORDER  BY id   -- the last two lines only to defend against ...
   -- FOR    UPDATE  -- ... concurrent writes and possible deadlocks
   ) upd
WHERE  t.id = upd.id
AND    t.test IS DISTINCT FROM upd.val;

避免更多的空更新。
如果 test 被定义为 NOT NULL 你可以简化为:

 AND    t.test <> upd.val;

比较这个相关答案的最后一章:

  • How do I (or can I) SELECT DISTINCT on multiple columns?

  • 只有最后一个变体需要 id 上的索引才能更快。应该被主键约束覆盖。