更新查询 10M 行中的 180k 行 table 出乎意料地慢
UPDATE query for 180k rows in 10M row table unexpectedly slow
我有一个 table 变得太大了,我想缩小它的尺寸
使用更新查询。此 table 中的某些数据是多余的,并且
我应该可以通过设置冗余来回收很多 space
“细胞”为空。但是,我的 UPDATE 查询占用过多
完成时间。
Table 详情
-- 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[]
这是我第一次尝试更新查询。我把它分成一些
临时 tables 只是为了让 id 更新。此外,为了减少
查询,我选择了一个日期 运行ge 为 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 tables 的查询在 1 秒内执行。我 运行 UPDATE 查询了一个小时(!),然后才最终将其终止。只有180k
行需要更新。似乎不需要那么多
是时候更新那么多行了。 Temp table B 准确识别哪个
要更新的行。
这是来自上述 UPDATE 查询的 EXPLAIN。这个解释的一个奇怪特征是它显示了 4.88M 行,但只有 180k 行要更新。
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 分钟就杀了它。感觉如果我知道这些技巧,我应该能够在更短的时间内 运行 这个查询。此查询在下面有 EXPLAIN。这个解释显示了 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)
编辑:接受的答案中的一个建议是在更新之前删除所有索引,然后再将它们添加回来。这就是我所做的,有点扭曲:我需要另一个 table 来保存已删除索引中的索引数据,以使 A 和 B 查询更快:
CREATE TABLE tempid AS
SELECT id, testid, band, date
FROM table1
我在这个 table 上为 id、testid 和日期创建了索引。然后我将 A 和 B 查询中的 table1
替换为 tempid
。它仍然比我希望的要慢,但它确实完成了工作。
您可能有另一个 table 有一个外键到这个 table 到一个或多个您设置为 NULL 的列。而这个国外的table在列上没有索引
每次您将行值设置为 NULL 时,数据库都必须检查外部 table - 也许它有一行引用了您要删除的值。
如果是这种情况,您应该可以通过在此遥控器上添加索引来加快速度 table。
例如,如果您有这样的 table:
create table table2 (
id serial primary key,
band integer references table1(data1)
)
然后就可以创建索引了create index table2_band_nnull_idx on table2(band) where band is not null
.
但是您建议您设置为 NULL 的所有列都具有数组类型。这意味着它们不太可能被引用。仍然值得一试。
另一种可能性是您在 table 上有一个触发器,它工作缓慢。
另一种可能是您在 table 上有很多索引。您更新的每一行都必须更新每个索引,并且它只能使用一个处理器内核。
有时删除所有索引、进行批量更新然后重新创建它们会更快。创建索引可以使用多个核心 - 每个索引一个核心。
另一种可能是您的查询正在等待其他查询完成并释放其锁。你应该检查:
select now()-query_start, * from pg_stat_activity where state<>'idle' order by 1;
我有一个 table 变得太大了,我想缩小它的尺寸 使用更新查询。此 table 中的某些数据是多余的,并且 我应该可以通过设置冗余来回收很多 space “细胞”为空。但是,我的 UPDATE 查询占用过多 完成时间。
Table 详情
-- 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[]
这是我第一次尝试更新查询。我把它分成一些 临时 tables 只是为了让 id 更新。此外,为了减少 查询,我选择了一个日期 运行ge 为 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 tables 的查询在 1 秒内执行。我 运行 UPDATE 查询了一个小时(!),然后才最终将其终止。只有180k 行需要更新。似乎不需要那么多 是时候更新那么多行了。 Temp table B 准确识别哪个 要更新的行。
这是来自上述 UPDATE 查询的 EXPLAIN。这个解释的一个奇怪特征是它显示了 4.88M 行,但只有 180k 行要更新。
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 分钟就杀了它。感觉如果我知道这些技巧,我应该能够在更短的时间内 运行 这个查询。此查询在下面有 EXPLAIN。这个解释显示了 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)
编辑:接受的答案中的一个建议是在更新之前删除所有索引,然后再将它们添加回来。这就是我所做的,有点扭曲:我需要另一个 table 来保存已删除索引中的索引数据,以使 A 和 B 查询更快:
CREATE TABLE tempid AS
SELECT id, testid, band, date
FROM table1
我在这个 table 上为 id、testid 和日期创建了索引。然后我将 A 和 B 查询中的 table1
替换为 tempid
。它仍然比我希望的要慢,但它确实完成了工作。
您可能有另一个 table 有一个外键到这个 table 到一个或多个您设置为 NULL 的列。而这个国外的table在列上没有索引
每次您将行值设置为 NULL 时,数据库都必须检查外部 table - 也许它有一行引用了您要删除的值。
如果是这种情况,您应该可以通过在此遥控器上添加索引来加快速度 table。
例如,如果您有这样的 table:
create table table2 (
id serial primary key,
band integer references table1(data1)
)
然后就可以创建索引了create index table2_band_nnull_idx on table2(band) where band is not null
.
但是您建议您设置为 NULL 的所有列都具有数组类型。这意味着它们不太可能被引用。仍然值得一试。
另一种可能性是您在 table 上有一个触发器,它工作缓慢。
另一种可能是您在 table 上有很多索引。您更新的每一行都必须更新每个索引,并且它只能使用一个处理器内核。
有时删除所有索引、进行批量更新然后重新创建它们会更快。创建索引可以使用多个核心 - 每个索引一个核心。
另一种可能是您的查询正在等待其他查询完成并释放其锁。你应该检查:
select now()-query_start, * from pg_stat_activity where state<>'idle' order by 1;