设置 LIMIT 时 Postgres SLOWER:除了添加虚拟“ORDER BY”之外如何修复?

Postgres SLOWER when a LIMIT is set: how to fix besides adding a dummy `ORDER BY`?

在 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 中添加一个虚拟 ID 以加快速度。我试图理解为什么。

运行 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)

我不是 SQL 专家,但我认为 Postgres 期望查询比实际更快,因此使用了一种实际上不合适的方法来获取数据,对吗?

数据库:

其他详细信息:

环境:

可变速度:

在我想到使用虚拟机 ORDER BY 之前,我尝试了这些:

None 有帮助。

问题:

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

谢谢!


类似问题:

  • PostgreSQL query very slow with limit 1 几乎是同一个问题,除了他的查询在 LIMIT 1 时很慢,而在 LIMIT 3 或更高时很好。然后当然数据不一样了。

这里的根本问题是所谓的提前中止查询计划。这是来自 pgsql-hackers 的一个线程,描述了类似的东西:

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

从那里引用,这就是为什么当存在 ORDER BY done DESC 时,规划器使用通常非常慢的索引扫描:

As usual, PostgreSQL is dramatically undercounting n_distinct: it shows chapters.user_id at 146,000 and the ratio of to_user_id:from_user_id as being 1:105 (as opposed to 1:6, which is about the real ratio). This means that PostgreSQL thinks it can find the 20 rows within the first 2% of the index ... whereas it actually needs to scan 50% of the index to find them.

在您的情况下,规划器认为如果它只是开始遍历按 done desc 排序的行(IOW,使用 review_done 索引),它将找到 4 行 clicker_id=28迅速地。由于需要按 "done" 降序返回行,因此它认为这将节省一个排序步骤并且比检索答题器 28 的所有行然后排序更快。考虑到行的真实世界分布,通常情况并非如此,需要它在找到 4 行之前跳过大量行,clicker=28。

一种更通用的处理方法是使用 CTE(在 9.6 中,它仍然是一个优化栅栏 - 这在 PG 12 中发生了变化,仅供参考)来获取没有顺序的行,然后在外部查询中添加 ORDER BY。鉴于为用户获取所有行,对它们进行排序,然后返回您需要的行数对于您的数据集来说是完全合理的(即使是 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;

这应该是可靠的快速,同时仍然遵守您想要的 ORDER BY 和 LIMIT。不过,我不确定这种模式是否有名称。