首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >使用大表更新是缓慢的,并且使用Seq扫描。

使用大表更新是缓慢的,并且使用Seq扫描。
EN

Database Administration用户
提问于 2022-06-15 15:26:42
回答 1查看 687关注 0票数 2

我有一个大表(最终可能有10亿行,但目前大约有2600万行),我想在给定分组的最高PK上为一个一次性批设置一个标志。

我选择创建一个临时表来存储应该设置为current=true的PKs,其余的应该设置为current=false。我做了一个临时的桌子,而不是一个物化的视图,但我认为这不会有什么真正的区别。

发现每个人的最大ID的过程并不太痛苦:

代码语言:javascript
复制
CREATE TABLE assertion (
    pk integer NOT NULL,
    a bigint NOT NULL,
    b bigint NOT NULL,
    c bigint NOT NULL,
    d integer NOT NULL,
    current boolean DEFAULT false NOT NULL
);

CREATE INDEX assertion_current_idx ON assertion USING btree (current) WHERE (current = true);
CREATE INDEX assertion_current_idx1 ON assertion USING btree (current);
CREATE UNIQUE INDEX assertion_a_b_c_d_idx ON assertion USING btree (a, b, c, d) WHERE (current = true);

SELECT COUNT(pk) FROM assertion;

-- 26916858
-- Time: 2912.403 ms (00:02.912)

CREATE TEMPORARY TABLE assertion_current AS
    (SELECT MAX(pk) as pk, a, b, c, d
      FROM assertion
      GROUP BY a, b, c, d);

-- Time: 72218.755 ms (01:12.219)

ANALYZE assertion_current;

CREATE INDEX ON assertion_current(pk);

-- Time: 22107.698 ms (00:22.108)

SELECT COUNT(pk) FROM assertion_current;

-- 26455092
-- Time: 15650.078 ms (00:15.650)

根据assertion_current的计数,我们需要为98%的行设置“当前”标志true。

棘手的问题是如何根据当前值在合理的时间内更新assertion表。a, b, c, d, current上有一个必须维护的唯一约束,因此current列的更新需要是原子的,以避免打破约束。

我有几个选择:

选项1

只更新那些更改的current值。这样做的好处是根据索引字段更新所需的最小行数:

代码语言:javascript
复制
BEGIN;
UPDATE assertion
   SET current = false
   WHERE assertion.current = true AND PK NOT IN (SELECT pk FROM assertion_current);
UPDATE assertion
   SET current = true
   WHERE assertion.current = false AND PK IN (SELECT pk FROM assertion_current);
COMMIT;

但是这两个查询都涉及到assertion_current上的序列扫描,我认为这需要乘以大量行。

代码语言:javascript
复制
Update on assertion  (cost=0.12..431141.55 rows=0 width=0)
   ->  Index Scan using assertion_current_idx on assertion  (cost=0.12..431141.55 rows=1 width=7)
         Index Cond: (current = true)
         Filter: (NOT (SubPlan 1))
         SubPlan 1
           ->  Materialize  (cost=0.00..787318.40 rows=29982560 width=4)
                 ->  Seq Scan on assertion_current  (cost=0.00..520285.60 rows=29982560 width=4)

代码语言:javascript
复制
 Update on assertion  (cost=595242.56..596693.92 rows=0 width=0)
   ->  Nested Loop  (cost=595242.56..596693.92 rows=17974196 width=13)
         ->  HashAggregate  (cost=595242.00..595244.00 rows=200 width=10)
               Group Key: assertion_current.pk
               ->  Seq Scan on assertion_current  (cost=0.00..520285.60 rows=29982560 width=10)
         ->  Index Scan using assertion_pkey on assertion  (cost=0.56..8.58 rows=1 width=10)
               Index Cond: (pk = assertion_current.pk)
               Filter: (NOT current)

这意味着其中一个查询(许多当前为真或许多当前为假)总是花费很长的时间。

选项2

一次传球,却不得不不必要地触摸每一排。

代码语言:javascript
复制
UPDATE assertion
   SET current =
     (CASE WHEN assertion.pk IN (select PK from assertion_current)
     THEN TRUE ELSE FALSE END)

但是这又导致了对assertion_current的序列扫描

代码语言:javascript
复制
 Update on assertion  (cost=0.00..15498697380303.70 rows=0 width=0)
   ->  Seq Scan on assertion  (cost=0.00..15498697380303.70 rows=35948392 width=7)
         SubPlan 1
           ->  Materialize  (cost=0.00..787318.40 rows=29982560 width=4)
                 ->  Seq Scan on assertion_current  (cost=0.00..520285.60 rows=29982560 width=4)

选项3

类似于选项1,但是在更新中使用WHERE

代码语言:javascript
复制
BEGIN;
UPDATE assertion SET current = false WHERE current = true;
UPDATE assertion SET current = true FROM assertion_current
  WHERE assertion.pk = assertion_current.pk;
COMMIT;

但是第二个查询涉及两个seq扫描:

代码语言:javascript
复制
 Update on assertion  (cost=1654256.82..2721576.65 rows=0 width=0)
   ->  Hash Join  (cost=1654256.82..2721576.65 rows=29982560 width=13)
         Hash Cond: (assertion_current.pk = assertion.pk)
         ->  Seq Scan on assertion_current  (cost=0.00..520285.60 rows=29982560 width=10)
         ->  Hash  (cost=1029371.92..1029371.92 rows=35948392 width=10)
               ->  Seq Scan on assertion  (cost=0.00..1029371.92 rows=35948392 width=10)

选项4

谢谢你,这花了6个小时,所以我取消了。

代码语言:javascript
复制
UPDATE assertion
   SET current = not current
   WHERE current <>
     (CASE WHEN assertion.pk IN (select PK from assertion_current)
     THEN TRUE ELSE FALSE END)

产生

代码语言:javascript
复制
 Update on assertion  (cost=0.00..11832617068493.14 rows=0 width=0)
   ->  Seq Scan on assertion  (cost=0.00..11832617068493.14 rows=27307890 width=7)
         Filter: (current <> CASE WHEN (SubPlan 1) THEN true ELSE false END)
         SubPlan 1
           ->  Materialize  (cost=0.00..787318.40 rows=29982560 width=4)
                 ->  Seq Scan on assertion_current  (cost=0.00..520285.60 rows=29982560 width=4)

选项5

谢谢@a_horse_with_no_name。这在我的机器上需要24分钟。

代码语言:javascript
复制
UPDATE assertion tg SET current = EXISTS (SELECT pk FROM assertion_current cr WHERE cr.pk = tg.pk);

给出

代码语言:javascript
复制
 Update on assertion tg  (cost=0.00..233024784.94 rows=0 width=0)
   ->  Seq Scan on assertion tg  (cost=0.00..233024784.94 rows=27445116 width=7)
         SubPlan 1
           ->  Index Only Scan using assertion_current_pk_idx on assertion_current cr  (cost=0.44..8.46 rows=1 width=0)
                 Index Cond: (pk = tg.pk)

是否有更好的方法来及时实现这一目标?

EN

回答 1

Database Administration用户

发布于 2022-06-15 18:49:32

我不认为你应该尽量避免每次扫描。但是,您不希望大量执行seq扫描(比如在未散列的子计划中,或者在嵌套循环的第二个子图中)。

在您的第一个计划中,它确实在一个未散列子计划中有一个seq扫描,但是它声称这将只执行一次,所以如果这是准确的话,就不会太糟糕了。但这似乎与你对“‘当前’标志在98%的行中是正确的”的描述相矛盾,所以也许这些统计数据是完全错误的。

您可以增加我们的work_mem,直到子计划切换到散列子计划为止,或者您可以将查询从NOT重写为NOT。

对于选项2,只需添加一个WHERE以消除不起作用的更新:

代码语言:javascript
复制
UPDATE assertion
   SET current = not current
   WHERE current <>  
     (CASE WHEN assertion.pk IN (select PK from assertion_current)
     THEN TRUE ELSE FALSE END)

但是再一次,使用现有的,而不是IN可能是更好的。

票数 1
EN
页面原文内容由Database Administration提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://dba.stackexchange.com/questions/313403

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档