【发布时间】:2020-11-25 12:04:31
【问题描述】:
我有 3 个表:请求、步骤和有效负载。每个请求有 N 个步骤(所以是一对多),每个步骤有 1 个有效载荷(一对一)。
现在,每当我想按有效负载主体进行过滤时,执行时间都很糟糕。
这是简化的请求:
select rh.id
from request_history_step_payload rhsp
join request_history_step rhs on rhs.id = rhsp.step_id
join request_history rh on rhs.request_id = rh.id
where rh.id> 35000 and rhs.step_type = 'CONSUMER_REQUEST' and rhsp.payload like '%09141%'
这是EXPLAIN ANALYZE(在VACUUM ANALYZE 之后立即运行):
Nested Loop (cost=0.71..50234.28 rows=1 width=8) (actual time=120.093..2494.929 rows=12 loops=1)
-> Nested Loop (cost=0.42..50233.32 rows=3 width=8) (actual time=120.083..2494.900 rows=14 loops=1)
-> Seq Scan on request_history_step_payload rhsp (cost=0.00..50098.28 rows=16 width=8) (actual time=120.063..2494.800 rows=25 loops=1)
Filter: ((payload)::text ~~ '%09141%'::text)
Rows Removed by Filter: 164512
-> Index Scan using request_history_step_pkey on request_history_step rhs (cost=0.42..8.44 rows=1 width=16) (actual time=0.003..0.003 rows=1 loops=25)
Index Cond: (id = rhsp.step_id)
Filter: ((step_type)::text = 'CONSUMER_REQUEST'::text)
Rows Removed by Filter: 0
-> Index Only Scan using request_history_pkey on request_history rh (cost=0.29..0.32 rows=1 width=8) (actual time=0.001..0.001 rows=1 loops=14)
Index Cond: ((id = rhs.request_id) AND (id > 35000))
Heap Fetches: 0
Planning Time: 0.711 ms
Execution Time: 2494.964 ms
现在,我想以某种方式建议计划者将LIKE 操作作为最后一站,但想不出任何方法。我已经尝试了各种连接,并对它们重新排序,并将条件移动到 ON 子句和 WHERE 子句之间,反之亦然。一切都无济于事!无论如何,它首先查看payload 表中的所有行,这显然是最糟糕的想法,考虑到其他条件,这可能会大大减少需要应用的LIKE 操作的数量。所以,我希望它会首先应用id 条件,它已经整理出所有记录的90%;然后它将应用step_type 条件,这将像其余的 85% 一样进行排序;因此只会将LIKE 条件应用于不到 5% 的所有有效负载。
我会怎么做呢?我正在使用 Postgres 11。
UPD:有人建议我为这些表添加索引信息,所以:
-
request_history- 在id字段上有 2 个唯一索引(我不知道为什么有 2 个) -
request_history_step- 有 2 个唯一索引,都在id字段上 -
request_history_step_payload- 在id字段上有 1 个唯一索引
UPD2:step 和 payload 表也定义了 FK(分别在 payload.step_id->step.id 和 step.request_id -> request_id 上)
我还尝试了几个(简化的)查询 w/sub-SELECT:
explain analyze select rhs.id from request_history_step rhs
join (select step_id from request_history_step_payload rhsp where rhsp.payload like '%09141%') rhsp on rhs.id = rhsp.step_id
where rhs.step_type = 'CONSUMER_REQUEST';
explain analyze select rhsp.step_id from request_history_step_payload rhsp
join (select id from request_history_step rhs where rhs.step_type = 'CONSUMER_REQUEST') rhs on rhs.id = rhsp.step_id
where rhsp.payload like '%09141%';
explain analyze select rhsp.step_id from request_history_step_payload rhsp
where rhsp.step_id in (select id from request_history_step rhs where rhs.step_type = 'CONSUMER_REQUEST')
and rhsp.payload like '%09141%';
(也使用JOIN LATERAL 而不仅仅是JOIN)- 每个 都给出了完全相同的计划,即嵌套循环,在它的嵌套循环中,以及“外部”(第一个)该循环中的一条腿是 SeqScan。这..让我发疯。为什么它要对尽可能多的行集执行最昂贵的操作??
UPD3:受原始问题下的 cmets 启发,我进行了一些进一步的实验。我用更简单的查询解决了:
select rhs.request_id
from request_history_step_payload rhsp
join request_history_step rhs on rhs.id = rhsp.step_id
where rhs.step_type = 'CONSUMER_REQUEST' and rhsp.payload like '%09141%';
现在,它的执行计划基本上和原来的一样,只是少了一个“嵌套循环”。
现在,我为payload.step_id 添加了索引:
create index request_history_step_payload_step_id on request_history_step_payload(step_id);
运行VACUUM ANALYZE;使用explain analyze 运行查询 - 没有任何变化。嗯。
现在我已经运行set enable_seqscan to off。 现在我们在谈:
Gather (cost=1000.84..88333.90 rows=3 width=8) (actual time=530.273..589.650 rows=14 loops=1)
Workers Planned: 1
Workers Launched: 1
-> Nested Loop (cost=0.84..87333.60 rows=2 width=8) (actual time=544.639..580.608 rows=7 loops=2)
-> Parallel Index Scan using request_history_step_pkey on request_history_step rhs (cost=0.42..15913.04 rows=20867 width=16) (actual time=0.029..28.667 rows=17620 loops=2)
Filter: ((step_type)::text = 'CONSUMER_REQUEST'::text)
Rows Removed by Filter: 64686
-> Index Scan using request_history_step_payload_step_id on request_history_step_payload rhsp (cost=0.42..3.41 rows=1 width=8) (actual time=0.031..0.031 rows=0 loops=35239)
Index Cond: (step_id = rhs.id)
Filter: ((payload)::text ~~ '%09141%'::text)
Rows Removed by Filter: 1
Planning Time: 0.655 ms
Execution Time: 589.688 ms
现在,随着 SeqScan 的成本飙升,我认为我们可以看到问题的要点:这个执行计划的成本被认为高于原来的(88k vs 50k),尽管执行时间实际上要短得多(590ms vs 2700ms)。这显然是 Postgre planner 一直选择“SeqScan first”的原因,尽管我努力说服他不这样做。
我也尝试为step.step_type 字段添加索引;基于hash 和btree。它们中的每一个仍然会生成一个成本超过 50k 的计划,因此将 enable_seqscan 设置为 on(默认值),计划者将始终忽略这些。
有人知道对此有什么缓解措施吗?我担心正确的解决方案可能需要更改计划变量的权重,当然我不倾向于这样做。但很高兴听到任何建议!
UPD4:现在我玩得更多了,我可以报告更多结果(enable_seqscan 设置为on:
这个很慢,应用seqscan,即使step.step_type上有索引:
explain analyze
select rhsp.step_id
from (select request_id, id from request_history_step rhs2 where rhs2.step_type = 'CONSUMER_REQUEST') rhs
join request_history_step_payload rhsp on rhs.id = rhsp.step_id
where rhsp.payload like '%09141%';
这是基于 O. Jones 的建议,仍然很慢:
explain analyze
with rhs as (select request_id, id from request_history_step rhs2 where rhs2.step_type = 'CONSUMER_REQUEST')
select rhsp.step_id from request_history_step_payload rhsp
join rhs on rhs.id = rhsp.step_id
where rhsp.payload like '%09141%';
但是这个稍作修改,快:
explain analyze
with rhs as (select request_id, id from request_history_step rhs2 where rhs2.step_type = 'CONSUMER_REQUEST')
select rhsp.step_id
from request_history_step_payload rhsp
join rhs on rhs.id = rhsp.step_id
where rhsp.step_id in (select id from rhs) and rhsp.payload like '%09141%';
它的执行计划是:
Hash Join (cost=9259.55..10097.04 rows=2 width=8) (actual time=1157.984..1162.199 rows=14 loops=1)
Hash Cond: (rhs.id = rhsp.step_id)
CTE rhs
-> Bitmap Heap Scan on request_history_step rhs2 (cost=1169.28..6918.06 rows=35262 width=16) (actual time=3.899..19.093 rows=35241 loops=1)
Recheck Cond: ((step_type)::text = 'CONSUMER_REQUEST'::text)
Heap Blocks: exact=3120
-> Bitmap Index Scan on request_history_step_step_type_hash (cost=0.00..1160.46 rows=35262 width=0) (actual time=3.047..3.047 rows=35241 loops=1)
Index Cond: ((step_type)::text = 'CONSUMER_REQUEST'::text)
-> CTE Scan on rhs (cost=0.00..705.24 rows=35262 width=8) (actual time=3.903..5.976 rows=35241 loops=1)
-> Hash (cost=2341.39..2341.39 rows=8 width=16) (actual time=1153.976..1153.976 rows=14 loops=1)
Buckets: 1024 Batches: 1 Memory Usage: 9kB
-> Nested Loop (cost=793.81..2341.39 rows=8 width=16) (actual time=104.170..1153.919 rows=14 loops=1)
-> HashAggregate (cost=793.39..795.39 rows=200 width=8) (actual time=33.315..44.875 rows=35241 loops=1)
Group Key: rhs_1.id
-> CTE Scan on rhs rhs_1 (cost=0.00..705.24 rows=35262 width=8) (actual time=0.001..23.590 rows=35241 loops=1)
-> Index Scan using request_history_step_payload_step_id on request_history_step_payload rhsp (cost=0.42..7.72 rows=1 width=8) (actual time=0.031..0.031 rows=0 loops=35241)
Index Cond: (step_id = rhs_1.id)
Filter: ((payload)::text ~~ '%09141%'::text)
Rows Removed by Filter: 1
Planning Time: 1.318 ms
Execution Time: 1162.618 ms
同样,成本大幅下降,而执行时间却没有那么多
【问题讨论】:
-
为了让条件
like '%09141%'使用索引,您需要创建一个 tirgram 索引 -
@a_horse_with_no_name 这不是我要解决的问题;我完全理解这样的事情需要很长时间;我不太明白为什么它被应用于整个表,而不仅仅是行的子集
-
将 enable_seqscan 设置为 off 的计划是什么?
-
您应该在
request_history_step_payload (step_id)和request_history_step(request_id)上有一个索引(可能是外键) -
我的意思是在这些列上创建索引。外键约束不会自动创建索引
标签: postgresql query-optimization