【问题标题】:Postgres subqueries running extremely slow, Why?Postgres 子查询运行速度极慢,为什么?
【发布时间】:2021-10-28 22:40:42
【问题描述】:

我发现有时响应缓慢的查询正在查看执行(解释分析)计划。

我觉得子查询的join部分比较慢,但是看执行计划,我觉得问题是循环很多。

这是查询:

LOG:  execute <unnamed>:
SELECT *,
       (SELECT COUNT(messaging_channelmembership.id)
        FROM messaging_channelmembership 
           INNER JOIN messaging_member
              ON (messaging_channelmembership.member_id = messaging_member.id) 
           INNER JOIN messaging_user
              ON (messaging_member.user_id = messaging_user.id) 
        WHERE (messaging_channelmembership.channel_id = messaging_message.channel_id
           AND messaging_channelmembership.last_read_message < messaging_message.id
           AND messaging_user.is_bot = false 
           AND messaging_user.username != 'puddlrbot'
           AND messaging_channelmembership.member_id != messaging_message.sender_id)
       ) as unread_count
FROM "messaging_message" 
WHERE ("messaging_message"."channel_id" = $1) 
   AND (messaging_message.id >= $2)
ORDER BY messaging_message.created
LIMIT 30;

解释分析

Limit  (cost=169.00..169.00 rows=1 width=101) (actual time=218.212..218.218 rows=30 loops=1)
  ->  Sort  (cost=169.00..169.00 rows=1 width=101) (actual time=218.211..218.213 rows=30 loops=1)
        Sort Key: messaging_message.created
        Sort Method: top-N heapsort  Memory: 32kB
        ->  Bitmap Heap Scan on messaging_message  (cost=105.15..168.99 rows=1 width=101) (actual time=0.898..217.929 rows=369 loops=1)
              Recheck Cond: ((channel_id = 1) AND (id >= 235589605))
              Heap Blocks: exact=12
              ->  BitmapAnd  (cost=105.15..105.15 rows=1 width=0) (actual time=0.371..0.371 rows=0 loops=1)
                    ->  Bitmap Index Scan on messaging_message_72eb6c85  (cost=0.00..32.53 rows=1596 width=0) (actual time=0.163..0.163 rows=2078 loops=1)
                          Index Cond: (channel_id = 1)
                    ->  Bitmap Index Scan on messaging_message_pkey  (cost=0.00..72.37 rows=3707 width=0) (actual time=0.142..0.142 rows=3066 loops=1)
                          Index Cond: (id >= 235589605)
              SubPlan 1
                ->  Aggregate  (cost=59.81..59.82 rows=1 width=4) (actual time=0.588..0.588 rows=1 loops=369)
                      ->  Nested Loop  (cost=1.27..59.80 rows=3 width=4) (actual time=0.008..0.578 rows=87 loops=369)
                            ->  Nested Loop  (cost=0.85..57.62 rows=3 width=8) (actual time=0.006..0.359 rows=87 loops=369)
                                  ->  Index Scan using messaging_channelmembership_72eb6c85 on messaging_channelmembership  (cost=0.42..32.27 rows=3 width=8) (actual time=0.004..0.051 rows=87 loops=369)
                                        Index Cond: (channel_id = messaging_message.channel_id)
                                        Filter: ((last_read_message < messaging_message.id) AND (member_id <> messaging_message.sender_id))
                                        Rows Removed by Filter: 3
                                  ->  Index Scan using messaging_member_pkey on messaging_member  (cost=0.42..8.44 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=32016)
                                        Index Cond: (id = messaging_channelmembership.member_id)
                            ->  Index Scan using messaging_user_pkey on messaging_user  (cost=0.42..0.72 rows=1 width=4) (actual time=0.002..0.002 rows=1 loops=32016)
                                  Index Cond: (id = messaging_member.user_id)
                                  Filter: ((NOT is_bot) AND ((username)::text <> 'puddlrbot'::text))
Planning time: 0.462 ms
Execution time: 218.258 ms

explain visualization

【问题讨论】:

  • 尝试将子查询重写为外连接,这样就可以获得比嵌套循环执行得更好的东西。
  • 如果可能,最好将相关子查询转换为子查询。
  • 你使用的是什么版本的 PostgreSQL?

标签: sql postgresql indexing sql-execution-plan


【解决方案1】:

如果您的查询运行缓慢,嵌套循环很可能是您的敌人。每当您在计划中看到这一点时,您很有可能想要摆脱它。始终值得尝试的一件简单的事情是让它使用哈希连接。

除了在例如SQL 服务器,在 postgresql 中,查询中似乎没有直接的方法来强制进行哈希连接。

这是一个可能有助于做到这一点的问题:

https://dba.stackexchange.com/questions/181674/undesirable-nest-loop-vs-hash-join-in-postgresql-9-6

作为临时 (!) 解决方案,您可以使用 enable_nestloop(false): https://www.postgresql.org/docs/13/runtime-config-query.html 禁用在计划器中嵌套的使用

【讨论】:

    【解决方案2】:

    子计划应该只运行 30 次,而不是 369 次。 v9.6 中添加了一项优化,这是仍然受支持的最旧版本,因此您确实需要升级。

    但您可以使用公用表表达式强制它这样做:

    with messaging_message as (
      select * FROM "messaging_message" 
      WHERE ("messaging_message"."channel_id" = $1) 
         AND (messaging_message.id >= $2)
      ORDER BY messaging_message.created
      LIMIT 30
    )
    SELECT *,
           (SELECT COUNT(messaging_channelmembership.id)
            FROM messaging_channelmembership 
               INNER JOIN messaging_member
                  ON (messaging_channelmembership.member_id = messaging_member.id) 
               INNER JOIN messaging_user
                  ON (messaging_member.user_id = messaging_user.id) 
            WHERE (messaging_channelmembership.channel_id = messaging_message.channel_id
               AND messaging_channelmembership.last_read_message < messaging_message.id
               AND messaging_user.is_bot = false 
               AND messaging_user.username != 'puddlrbot'
               AND messaging_channelmembership.member_id != messaging_message.sender_id)
           ) as unread_count
    from messaging_message;
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-02-29
      • 2018-04-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多