我有一个大表(最终可能有10亿行,但目前大约有2600万行),我想在给定分组的最高PK上为一个一次性批设置一个标志。
我选择创建一个临时表来存储应该设置为current=true的PKs,其余的应该设置为current=false。我做了一个临时的桌子,而不是一个物化的视图,但我认为这不会有什么真正的区别。
发现每个人的最大ID的过程并不太痛苦:
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列的更新需要是原子的,以避免打破约束。
我有几个选择:
只更新那些更改的current值。这样做的好处是根据索引字段更新所需的最小行数:
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上的序列扫描,我认为这需要乘以大量行。
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)和
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)这意味着其中一个查询(许多当前为真或许多当前为假)总是花费很长的时间。
一次传球,却不得不不必要地触摸每一排。
UPDATE assertion
SET current =
(CASE WHEN assertion.pk IN (select PK from assertion_current)
THEN TRUE ELSE FALSE END)但是这又导致了对assertion_current的序列扫描
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)类似于选项1,但是在更新中使用WHERE:
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扫描:
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)谢谢你,这花了6个小时,所以我取消了。
UPDATE assertion
SET current = not current
WHERE current <>
(CASE WHEN assertion.pk IN (select PK from assertion_current)
THEN TRUE ELSE FALSE END)产生
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)谢谢@a_horse_with_no_name。这在我的机器上需要24分钟。
UPDATE assertion tg SET current = EXISTS (SELECT pk FROM assertion_current cr WHERE cr.pk = tg.pk);给出
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)是否有更好的方法来及时实现这一目标?
发布于 2022-06-15 18:49:32
我不认为你应该尽量避免每次扫描。但是,您不希望大量执行seq扫描(比如在未散列的子计划中,或者在嵌套循环的第二个子图中)。
在您的第一个计划中,它确实在一个未散列子计划中有一个seq扫描,但是它声称这将只执行一次,所以如果这是准确的话,就不会太糟糕了。但这似乎与你对“‘当前’标志在98%的行中是正确的”的描述相矛盾,所以也许这些统计数据是完全错误的。
您可以增加我们的work_mem,直到子计划切换到散列子计划为止,或者您可以将查询从NOT重写为NOT。
对于选项2,只需添加一个WHERE以消除不起作用的更新:
UPDATE assertion
SET current = not current
WHERE current <>
(CASE WHEN assertion.pk IN (select PK from assertion_current)
THEN TRUE ELSE FALSE END)但是再一次,使用现有的,而不是IN可能是更好的。
https://dba.stackexchange.com/questions/313403
复制相似问题