代码之家  ›  专栏  ›  技术社区  ›  Fabien Snauwaert

当设置了限制时,Postgres会变慢:除了添加一个伪“ORDER BY”之外,如何修复?

  •  0
  • Fabien Snauwaert  · 技术社区  · 5 年前

    在Postgres中,一些查询在添加 LIMIT

    询问:

    SELECT * FROM review WHERE clicker_id=28 ORDER BY done DESC LIMIT 4; -- 51 sec
    SELECT * FROM review WHERE clicker_id=28 ORDER BY id, done DESC LIMIT 4; -- 0.020s
    SELECT * FROM review WHERE clicker_id=28 LIMIT 4; -- 0.007s
    SELECT * FROM review WHERE clicker_id=28 ORDER BY id; -- 0.007s
    

    如您所见,我需要在 ORDER BY

    跑步 EXPLAIN 在他们身上:

    EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY done DESC LIMIT 4;
    EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY id, done DESC LIMIT 4;
    EXPLAIN SELECT * FROM review WHERE clicker_id=28 LIMIT 4;
    EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY id;
    

    给出:

    EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY done DESC LIMIT 4
    Limit  (cost=0.44..249.76 rows=4 width=56)
      ->  Index Scan using review_done on review  (cost=0.44..913081.13 rows=14649 width=56)
            Filter: (clicker_id = 28)
    
    
    EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY id, done DESC LIMIT 4
    Limit  (cost=11970.75..11970.76 rows=4 width=56)
      ->  Sort  (cost=11970.75..12007.37 rows=14649 width=56)
            Sort Key: id, done DESC
            ->  Index Scan using review_clicker_id on review  (cost=0.44..11751.01 rows=14649 width=56)
                  Index Cond: (clicker_id = 28)
    
    
    EXPLAIN SELECT * FROM review WHERE clicker_id=28 LIMIT 4
    Limit  (cost=0.44..3.65 rows=4 width=56)
      ->  Index Scan using review_clicker_id on review  (cost=0.44..11751.01 rows=14649 width=56)
            Index Cond: (clicker_id = 28)
    
    
    EXPLAIN SELECT * FROM review WHERE clicker_id=28 ORDER BY id
    Sort  (cost=12764.61..12801.24 rows=14649 width=56)
      Sort Key: id
      ->  Index Scan using review_clicker_id on review  (cost=0.44..11751.01 rows=14649 width=56)
            Index Cond: (clicker_id = 28)
    

    数据库:

    • 这个 review
      • 包含2200多万行。
        • 一个给定的用户最多可以得到7066行。
        • 测试中的那个(id 28)当时有288个。
      • 具有以下结构:
        • id:bigint自动递增[nextval('public.review_id_seq')]
        • 类型:审查类型为空
        • 迭代:smallint NULL
        • 到期:timestamptz空
        • 完成:timestamptz NULL
        • clicker_id:bigint空
        • 独白:bigint空
        • 独特类型,点击者id,独白id,迭代
        • 索引单击器id
        • 索引完成,到期,独白
        • 索引id
        • 索引完成描述

    其他详细信息:

    环境:

    • 将查询运行到生产环境(Heroku Postgres,9.6.16版)中,差异不大,但仍然不大:缓慢的查询可能需要600毫秒。

    变速:

    • 有时,相同的查询(无论是完全相同的,还是不同的点击器id)运行得更快(不到1秒),但我不明白为什么。我需要他们 一贯地 快。
    • 如果我用 LIMIT 288

    在我想到使用假人之前 订货人

    没有帮助。

    我的问题本身已经解决了,但我不满意:

    • 有没有 此“模式”的名称 订货人
    • 我怎么能发现 将来会有这样的问题吗?(这花了很长时间才想出来。)除非我漏掉了什么,否则 解释 不是很有用:
    • 备选方案: 还有别的办法处理吗? (因为这个解决方案感觉像是一个黑客。)


    类似问题:

    0 回复  |  直到 5 年前
        1
  •  1
  •   AdamKG    5 年前

    这里的根本问题是所谓的中止早期查询计划。以下是pgsql黑客的一条线索,描述了类似的情况:

    https://www.postgresql.org/message-id/541A2335.3060100%40agliodbs.com

    与往常一样,PostgreSQL显著地低估了n}u distinct:它显示 是1:105(而不是1:6,这是真正的比例)。这个 索引的。。。而实际上它需要扫描50%的索引

    在您的例子中,计划器认为,如果它只是开始遍历done desc排序的行(low,使用review_done索引),它将快速找到4行clicker_id=28。由于行需要按“完成”降序返回,它认为这将保存排序步骤,并且比检索clicker 28的所有行然后进行排序要快。考虑到实际的行分布,结果往往不是这样,这要求它在找到clicker=28的4之前跳过大量的行。

    CTE (在9.6中,这仍然是一个优化界限-这在第12页中有所改变,仅供参考)获取没有order by的行,然后在外部查询中添加orderby。考虑到为用户获取所有行、对它们进行排序并返回所需的行数对数据集来说是完全合理的(即使7k行的单击器也不应该是问题),您可以通过在CTE中没有ORDER by或LIMIT来防止计划员认为中止早期计划将是最快的,并提供如下查询:

    WITH clicker_rows as (SELECT * FROM review WHERE clicker_id=28)
    select * From clicker_rows ORDER BY done DESC LIMIT 4;
    

    推荐文章