繁体   English   中英

如何提高 PostgreSQL 查询性能

[英]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: steppayload表也定义了 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM