在 postgres 中使用 LIMIT 时不使用索引

Posted

技术标签:

【中文标题】在 postgres 中使用 LIMIT 时不使用索引【英文标题】:Index not used when LIMIT is used in postgres 【发布时间】:2011-12-19 20:11:28 【问题描述】:

我有一个带有 (language_id, state) 索引的单词表。以下是 EXPLAIN ANALYZE 的结果:

无限制

explain analyze SELECT "words".* FROM "words" WHERE (words.language_id = 27) AND (state IS NULL);

Bitmap Heap Scan on words  (cost=10800.38..134324.10 rows=441257 width=96) (actual time=233.257..416.026 rows=540556 loops=1)
Recheck Cond: ((language_id = 27) AND (state IS NULL))
->  Bitmap Index Scan on ls  (cost=0.00..10690.07 rows=441257 width=0) (actual time=230.849..230.849 rows=540556 loops=1)
Index Cond: ((language_id = 27) AND (state IS NULL))
Total runtime: 460.277 ms
(5 rows)

限制 100

explain analyze SELECT "words".* FROM "words" WHERE (words.language_id = 27) AND (state IS NULL) LIMIT 100;

Limit  (cost=0.00..51.66 rows=100 width=96) (actual time=0.081..0.184 rows=100 loops=1)
->  Seq Scan on words  (cost=0.00..227935.59 rows=441257 width=96) (actual time=0.080..0.160 rows=100 loops=1)
Filter: ((state IS NULL) AND (language_id = 27))
Total runtime: 0.240 ms
(4 rows)

为什么会这样?我怎样才能让索引在所有情况下都可以使用?

谢谢。

【问题讨论】:

没有 ORDER BY 的 LIMIT 似乎具有有限(没有双关语)的价值。您希望返回哪 100 行? 顺便说一句:language_id=17 AND status IS NULL 子句的选择性是什么?单词表的总大小是多少? true... 订单发生在 id DESC 上。会不会让它慢下来?该列是否需要索引? @wildplasser 总大小为 1000 万行,选择性约为 500,000 行 因此您希望查询获取 5% 的数据。计划取决于关键结构(+“集群”)+统计信息的可用性和形状+元组大小(->每页估计的元组数->需要获取的页数)。 【参考方案1】:

我认为 PostreSQL 查询规划器只是认为在第二种情况下 - 具有 LIMIT 的情况 - 不值得应用索引,因为它 [LIMIT] 太小了。所以这不是问题。

【讨论】:

你是对的,但我怎样才能避免这种情况呢?实际上我正在使用 jsonb gin 索引,由于限制为 1,这个索引没有被使用并且需要大量时间。 嗨@Anurag,你是如何解决这个问题的?【参考方案2】:

查看有关 Using EXPLAIN 和 Query Planning 的 PostgreSQL 文档。在LIMIT 100 的情况下,查询计划器更喜欢顺序扫描而不是索引扫描的原因仅仅是因为顺序扫描更便宜。

查询中没有ORDER BY 子句,因此规划器对匹配过滤条件的前100 行(随机)是可以的。索引扫描需要先读取索引页,然后读取数据页以获取相应的行。顺序扫描只需要读取数据页来获取行。在您的情况下,表统计信息似乎表明有足够的(随机)行与过滤条件匹配。顺序页读取以获取 100 行的成本被认为比先读取索引然后获取实际行的成本便宜。当您提高限制或匹配过滤条件的行数减少时,您可能会看到不同的计划。

在默认设置下,规划器认为随机页面读取的成本 (random_page_cost) 是顺序页面读取成本 (seq_page_cost) 的四倍。可以调整这些设置以调整查询计划(例如,当整个数据库在 RAM 中时,随机页面读取并不比顺序页面读取更昂贵,并且应该首选索引扫描)。您还可以通过启用/禁用某些类型的扫描来尝试不同的查询计划,例如:

set enable_seqscan = [on | off]
set enable_indexscan = [on | off]

虽然可以在全局范围内启用/禁用某些类型的扫描,但这应该仅用于在每个会话的基础上进行临时调试或故障排除。

在测试查询计划之前还要运行 VACUUM ANALYZE words,否则测试之间运行的自动清理 (autovaccum) 可能会影响结果。

【讨论】:

【参考方案3】:

无限制:行=540556 循环=1 总运行时间:460.277 毫秒

有限制:行=100 循环=1 总运行时间:0.240 毫秒

我认为这里没有问题。如果您的查询产生 500K 行,则需要更多时间。

【讨论】:

【参考方案4】:

这两个查询返回不同数量的行也很奇怪。我猜你一直在插入...呃,如果你做一个子选择呢?

select * from (select ...) limit 100;

【讨论】:

"(5 rows)" 是指解释计划中的行数,而不是查询返回的行数

以上是关于在 postgres 中使用 LIMIT 时不使用索引的主要内容,如果未能解决你的问题,请参考以下文章

使用 LIMIT / ORDER BY 和 pg Postgres NodeJS 作为参数

使用 QProcess 启动 pg_dumpall 时不起作用

Postgres:左连接,order by和limit 1

如何在 SQL Server 2005 中使用 LIMIT [X] OFFSET [Y] [重复]

Postgres 从动态 sql 字符串创建本地临时表(在提交删除时)

从Spark limit()函数重新分区数据帧