[英]How to improve PostgreSQL query performance
我有 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
条件,该条件将排序为 rest 的 85%; 因此只会将LIKE
条件应用于不到 5% 的所有有效载荷。
我怎么会 go 呢? 我正在使用 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:受原始问题下评论的启发,我进行了一些进一步的实验。 我解决了更简单的查询:
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 对 50k),尽管执行时间实际上很远更短(590 毫秒对 2700 毫秒)。 这显然是 Postgre 计划者继续选择“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
同样,成本大幅下降,而执行时间却没有那么多
从您的计划看来,您对request_history_step_payload
操作的step_type
谓词没有多大帮助。
因此,让我们尝试一个包含包含列的文本(三元组)索引,以帮助该搜索步骤更快地工作。
CREATE INDEX CONCURRENTLY rhsp_type_payload
ON request_history_step_payload
USING GIN (step_type gin_trgm_ops)
INCLUDE (rhs_step_type, rhs_step_id);
有可能会有所帮助。 试试看。
当您拥有该索引时,您还可以尝试像这样重新处理您的查询:
select rh.id
from ( select step_id
from request_history_step_payload
where rhs.step_type = 'CONSUMER_REQUEST'
and rhsp.payload like '%09141%'
) 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
这会将您对该request_history_step_payload
表的搜索移至子查询。 您可以单独优化子查询,因为您尝试让所有这些都以足够快的速度为您的应用程序工作。
并且,删除任何重复的索引。 它们无缘无故减慢了 INSERT 和 UPDATE 操作。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.