【问题标题】:Query using group by and max() will not use index使用 group by 和 max() 查询不会使用索引
【发布时间】:2020-12-27 20:50:27
【问题描述】:

我有一个非常简单的查询:

select id, max(time) AS max_time
    from table
    group by id;

我还使用 id + time 创建了一个索引:

CREATE INDEX table_time_and_id ON table USING (id, time DESC NULLS LAST)

(我假设 id 已经有一个索引,因为它是一个外键,时间可以是 NULL)

问题是,如果我explainselect,我看到这个索引没有被使用;我期望对于每个id,它必须只选择第一个元素,因为它已经是max(),并且几乎是立即的;相反,它正在执行顺序读取,大约需要 16 秒。

我试图通过SET LOCAL enable_seqscan = off; 强制使用索引,而在它执行并使用正确的索引时,大约需要 1 分钟!

为什么这个索引变慢了,而不是让查询变得更快?我该如何解决?

数据库是postgreeSQL。

【问题讨论】:

    标签: sql postgresql select indexing group-by


    【解决方案1】:

    I assume id already has an index as primary key 是什么意思:如果它是主键,它应该是唯一的?你为什么要按 id 分组?

    It is possible to do this group by,不过就是这样

    select id, time from table
    

    Tt 应该更快地读取整个表,因为查询不必检查可见性映射,from the documentation about covering indexes

    简而言之,虽然考虑到两个基本要求,仅索引扫描是可能的,但只有当表的大部分堆页设置了它们的所有可见映射位时,它才会是一个胜利。但是大部分行不变的表很常见,因此这种类型的扫描在实践中非常有用。

    两个基本要求是:索引类型(如btree)允许,并且查询的所有列都在索引内。

    你也应该阅读the wiki page

    在 Postgres 9.2 中,收集了关于已知全部可见的页面比例的统计信息。 pg_class.relallvisible 列表示有多少页是可见的(比例可以通过计算它作为pg_class.relpages 的比例得到)。这些统计信息在 VACUUM 运行时更新。 建议升级到 PostgreSQL 9.2 后立即运行 VACUUM ANALYZE,以确保 relallvisible 大致符合实际情况。

    也许您的统计信息没有更新,也许您的大部分页面似乎不可见。

    如果您在执行查询之前添加索引(作为测试),它可能会使用索引:at least sometimes the optimizer will use the fresh index

    在小提琴中,您可以看到在 VACUUM ANALYZE 之后,它更快,并且更快,因为没有 Heap Fetches: 0。但是表中只有 2 列,顺序扫描仍然更快。

    【讨论】:

    • > 我假设 id 已经有一个索引作为主键是什么意思:如果它是主键,它应该是唯一的?你为什么要按 id 分组?你完全正确,我被 ID 名弄糊涂了,但不是唯一键,它是外部键,所以有很多
    • @Lesto 如果表中有更多列,那么覆盖索引会变得更快,如果您执行VACUUM ANALYZE table,行为会改变吗?
    • 接受这个作为答案,不知道我是怎么忘记这个问题的,但这是有更多信息可以深入挖掘的答案
    【解决方案2】:

    首先,让我指出 Postgres 优化器正在做它应该做的事情。尽管您尽最大努力绕过它,但它正在选择最佳优化计划。但 。 . .这没有回答你的问题。

    通常,在 Postgres 中使用索引时,引擎需要检查原始数据页以查看这些值是否对当前事务可见。这是支持数据库的 ACID 属性所必需的,其中数据同时被修改和查询。 (我应该注意,这只是实现这些属性的一种方式。)

    Postgres 支持仅索引扫描有一段时间了——但它们假定所有行对当前事务都是可见的。这在相对静态的数据库中通常是正确的,特别是当它们在任何更改后被清理时。

    如果不知道这些行是否可见,则需要读取索引和原始数据。乱序读取原始数据会带来额外的开销,尤其是在内存有限的环境中。也就是说,如果整个表不适合可用内存,那么您最终会“颠簸”。但即使只是乱序读取表格也比扫描表格更昂贵。

    您可以在documentation 中阅读有关仅索引扫描的更多信息。

    【讨论】:

    • 我正在使用的索引 table_time_and_id 包含我需要的所有字段。我还看到您最终将如何进行许多小型随机访问,而不是可能较小但较大的顺序读取,但是我现在对于每个 ID 都有数万次记录,因此在这种情况下,稀疏读取应该更有效,这让我感到困惑。我想改为创建一个只有每个索引的最大值的material view,但是我必须确保每次table 更改时手动更新它。
    【解决方案3】:

    如果将表扫描减少到最小,索引扫描是完美的(当只应从磁盘读取表的较小部分时)。当结果较大时,索引扫描比 seq 扫描慢。索引扫描使用较慢的随机 IO 并以随机顺序迭代表。 seq scan使用seq IO,比random IO要快,但是需要从磁盘读取完整的表数据文件。

    【讨论】:

      【解决方案4】:

      PostgreSQL 有一个非常先进的智能系统,通常最好让它选择最佳的索引形式。

      很可能他选择忽略他的索引,因为它变得更快。

      无论如何,创建您的索引并准备它,如果 Postgres 认为合适,它将使用它。

      对不起我的英语不好

      【讨论】:

        【解决方案5】:

        您需要的是“索引跳过扫描”或“松散索引扫描”。但是 PostgreSQL 并没有自动实现这些(还)。无论如何,您都可以通过使用recursive CTE 来强制解决问题,但这很难、容易出错且丑陋。 (幸运的是你可以把它包装在一个视图中)

        【讨论】:

          猜你喜欢
          • 2021-08-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-09
          • 1970-01-01
          • 2011-08-05
          • 1970-01-01
          相关资源
          最近更新 更多