【发布时间】:2020-10-27 22:46:37
【问题描述】:
我的桌子太大了,我想缩小它的大小 使用 UPDATE 查询。该表中的某些数据是多余的,并且 我应该能够通过设置冗余来回收大量空间 “细胞”为 NULL。但是,我的 UPDATE 查询占用过多 完成所需的时间。
表格详情
-- table1 10M rows (estimated)
-- 45 columns
-- Table size 2200 MB
-- Toast Table size 17 GB
-- Indexes Size 1500 MB
-- **columns in query**
-- id integer primary key
-- testid integer foreign key
-- band integer
-- date timestamptz indexed
-- data1 real[]
-- data2 real[]
-- data3 real[]
这是我第一次尝试更新查询。我把它分成了一些 临时表只是为了更新 id。此外,为了减少 查询,我选择了 2020 年 6 月的日期范围
CREATE TEMP TABLE A as
SELECT testid
FROM table1
WHERE date BETWEEN '2020-06-01' AND '2020-07-01'
AND band = 3;
CREATE TEMP TABLE B as -- this table has 180k rows
SELECT id
FROM table1
WHERE date BETWEEN '2020-06-01' AND '2020-07-01'
AND testid in (SELECT testid FROM A)
AND band > 1
UPDATE table1
SET data1 = Null, data2 = Null, data3 = Null
WHERE id in (SELECT id FROM B)
用于创建 TEMP 表的查询在 1 秒内执行。在我最终杀死它之前,我运行了 UPDATE 查询一个小时(!)。只有180k 需要更新的行。似乎不需要那么多 是时候更新那么多行了。临时表 B 准确地确定了哪个 要更新的行。
这是来自上述 UPDATE 查询的 EXPLAIN。这个解释的一个奇怪的特点是它显示了 488 万行,但只有 18 万行需要更新。
Update on table1 (cost=3212.43..4829.11 rows=4881014 width=309)
-> Nested Loop (cost=3212.43..4829.11 rows=4881014 width=309)
-> HashAggregate (cost=3212.00..3214.00 rows=200 width=10)
-> Seq Scan on b (cost=0.00..2730.20 rows=192720 width=10)
-> Index Scan using table1_pkey on table1 (cost=0.43..8.07 rows=1 width=303)
Index Cond: (id = b.id)
运行此查询的另一种方法是一次性:
WITH t as (
SELECT id from table1
WHERE testid in (
SELECT testid
from table1
WHERE date BETWEEN '2020-06-01' AND '2020-07-01'
AND band = 3
)
)
UPDATE table1 a
SET data1 = Null, data2 = Null, data3 = Null
FROM t
WHERE a.id = t.id
在我杀死它之前,我只运行了大约 10 分钟。感觉如果我知道技巧,我应该能够在更短的时间内运行这个查询。这个查询在下面有解释。这个解释显示了 195k 行,这是更符合预期的,但成本要高得多 @ 1.3M 到 1.7M
Update on testlog a (cost=1337986.60..1740312.98 rows=195364 width=331)
CTE t
-> Hash Join (cost=8834.60..435297.00 rows=195364 width=4)
Hash Cond: (testlog.testid = testlog_1.testid)
-> Seq Scan on testlog (cost=0.00..389801.27 rows=9762027 width=8)
-> Hash (cost=8832.62..8832.62 rows=158 width=4)"
-> HashAggregate (cost=8831.04..8832.62 rows=158 width=4)
-> Index Scan using amptest_testlog_date_idx on testlog testlog_1 (cost=0.43..8820.18 rows=4346 width=4)
Index Cond: ((date >= '2020-06-01 00:00:00-07'::timestamp with time zone) AND (date <= '2020-07-01 00:00:00-07'::timestamp with time zone))
Filter: (band = 3)
-> Hash Join (cost=902689.61..1305015.99 rows=195364 width=331)
Hash Cond: (t.id = a.id)
-> CTE Scan on t (cost=0.00..3907.28 rows=195364 width=32)
-> Hash (cost=389801.27..389801.27 rows=9762027 width=303)
-> Seq Scan on testlog a (cost=0.00..389801.27 rows=9762027 width=303)
编辑:已接受答案中的建议之一是在更新之前删除所有索引,然后再将它们添加回来。这就是我所采用的方法,有一个转折:我需要另一个表来保存已删除索引中的索引数据,以使 A 和 B 查询更快:
CREATE TABLE tempid AS
SELECT id, testid, band, date
FROM table1
我在此表上为 id、testid 和日期创建了索引。然后我将 A 和 B 查询中的 table1 替换为 tempid。它仍然比我希望的要慢,但它确实完成了工作。
【问题讨论】:
-
代替
where id in (select id from B),试试where exists (select 1 from B where B.id = table1.id)。它可能会有所作为。另外,一定要在表B中索引id -
请向我们展示查询的
EXPLAIN执行计划。 -
@jjanes,为两个版本的 UPDATE 查询添加了 EXPLAIN 计划。
-
与您的问题无关,但是:Postgres 9.3 是no longer supported,您应该尽快计划升级。
-
@a_horse_with_no_name。你说得对。是时候更新了。我忽略了这个数据库,因为它工作得很好。但现在我知道是时候给它一些爱了。