【问题标题】:Why this query is not using index only scan in postgresql为什么此查询在 postgresql 中不使用仅索引扫描
【发布时间】:2015-06-10 15:09:24
【问题描述】:

我有一个包含 16 列的表,其中有一个主键和一个用于存储值的列。 我想选择一定范围内的所有值。 值列 (easyid) 已被索引。

create table tb1 (
    id Int primary key,
    easyid Int,
    .....
)
create index i_easyid on tb1 (easyid)

其他信息:postgresql 9.4,没有自动清理。 sql是这样的。

select "easyid" from "tb1" where "easyid" between 12183318 and 82283318

理论上,postgresql 应该对i_easyid 使用仅索引扫描。 仅在"easyid" between A and B 范围较小时才进行索引扫描。 当范围很大时,即B-A 是一个相当大的数字,postgresql 对i_easyid 使用位图索引扫描,然后对tb1 进行位堆扫描。

我说索引扫描是否取决于范围大小是错误的。 我用不同的参数尝试了相同的查询,有时只是索引扫描,有时不是。

tb1非常大,高达17G。 i_easyid 是 600MB。

这里是sql的解释。而且我不明白为什么 4000 行会花费超过 10 秒。

sample_pg=# explain analyze select easyid from tb1 where "easyid" between 152183318 and 152283318;
                                                         QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
 Bitmap Heap Scan on tb1  (cost=97.70..17227.71 rows=4416 width=4) (actual time=1.155..14346.311 rows=5004 loops=1)
   Recheck Cond: ((easyid >= 152183318) AND (easyid <= 152283318))
   Heap Blocks: exact=4995
   ->  Bitmap Index Scan on i_easyid  (cost=0.00..96.60 rows=4416 width=0) (actual time=0.586..0.586 rows=5004 loops=1)
         Index Cond: ((easyid >= 152183318) AND (easyid <= 152283318))
 Planning time: 0.080 ms
 Execution time: 14348.037 ms
(7 rows)

这是一个仅索引扫描的示例:

sample_pg=# explain analyze verbose select easyid from tb1 where "easyid" between 32280318 and 32283318;
                                                               QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------
 Index Only Scan using i_easyid on public.tb1  (cost=0.44..281.82 rows=69 width=4) (actual time=14.585..160.624 rows=33 loops=1)
   Output: easyid
   Index Cond: ((tb1.easyid >= 32280318) AND (tb1.easyid <= 32283318))
   Heap Fetches: 33
 Planning time: 0.085 ms
 Execution time: 160.654 ms
(6 rows)

【问题讨论】:

  • 向我们展示explain (analyze, verbose)的输出
  • 您的表中可能没有足够的数据让规划者来处理索引。要查看是否会使用该索引,请在控制台中输入 set enable_seqscan = off; 并重试。这将使 PostgreSql 尽可能避免顺序扫描。
  • @a_horse_with_no_name 解释添加
  • (auto)vacuum 正在运行?顺便说一句,您的 Postgres 版本是什么?而real表定义,我想rowsize大于8字节)
  • 你到底为什么要关闭 autovacuum?

标签: sql postgresql


【解决方案1】:

autovacuum 没有运行

PostgreSQL 仅索引扫描需要一些关于哪些行对当前事务“可见”的信息 - 即未删除、更新行的旧版本以及未提交的插入或更新的新版本。

此信息保存在“可见性地图”中。

可见性地图由 VACUUM 维护,通常由 autovacuum 工作人员在后台维护。

如果 autovacuum 不能很好地跟上写入活动,或者如果 autovacuum 已被禁用,则可能不会使用仅索引扫描,因为 PostgreSQL 将看到可见性映射没有足够的表数据。

重新打开自动清理。然后手动VACUUM 表立即更新。

顺便说一句,除了可见性地图信息,autoVACUUM 还可以写入提示位信息,可以使SELECTs 最近插入/更新的数据更快。

Autovacuum 还维护对有效查询计划至关重要的表统计信息。关闭它会导致规划者使用越来越陈旧的信息。

这对于防止称为事务 ID 环绕的问题也绝对重要,这是一种紧急情况,可能导致整个数据库进入紧急关闭状态,直到耗时执行全表VACUUM

不要关闭自动吸尘器

至于为什么有时使用仅索引扫描,有时不使用,有几种可能:

  • 当前的random_page_cost 设置让它认为随机 I/O 会比实际速度慢,因此它会尽量避免它;

  • 表格统计信息,尤其是限制值,已过时。所以它没有意识到在仅索引扫描中很有可能会快速发现正在查找的值;

  • 可见性映射已过时,因此它认为仅索引扫描会发现太多需要堆提取来检查的值,这使得它比其他方法慢,特别是如果它认为值的比例可能是发现是高。

这些问题中的大部分都可以通过不使用 autovacuum 来解决。事实上,在频繁附加的表上,您应该将 autovacuum 设置为比默认值运行更频繁,这样它会更多地更新限制统计信息。 (这样做有助于解决 PostgreSQL 的规划器问题,其中最常查询的数据是最近插入的,具有递增的 ID 或时间戳,这意味着最需要的值永远不会出现在表直方图和限制统计信息中)。

重新开启 autovacuum - 然后将其打开。

【讨论】:

  • 我想我犯了一个错误。我的自动吸尘器打开了。我认为这是一个单独的过程,我没有用ps -aux | grep vacuum 找到它。我以这种方式解决问题:CLUSTER tb1 USING i_easyid。现在范围选择要快得多,并且大部分时间都使用index scan only
【解决方案2】:

我不能 100% 确定,但我怀疑 PostgreSQL 认为读取表会比读取索引更快,因为 random_page_cost。索引读取的成本可能更高,因为需要在其中找到基本上随机的页面。

从表中检索到的数据需要排序,但计算可能表明(顺序表读取 + 排序)的总成本大于(随机索引读取)。

这可以通过更改 random_page_cost 的值进行部分测试,如果您使用非常快的磁盘或 SSD,这将值得研究。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-23
    • 2015-08-09
    • 2017-06-12
    • 2020-09-06
    • 1970-01-01
    • 1970-01-01
    • 2018-10-09
    • 2021-08-15
    相关资源
    最近更新 更多