← 返回题目列表

PostgreSQL 覆盖索引 INCLUDE 是什么?和联合索引有什么区别?

中等 第 23 / 31 题 更新于 2026/07/30
PostgreSQL覆盖索引INCLUDE

简化版

INCLUDE 可以把非搜索条件列附加到索引叶子中,让查询有机会做 index-only scan。它和联合索引的区别是:联合索引键参与排序和查找,INCLUDE 列主要用于覆盖返回字段,不参与索引查找顺序。

详细版

如果查询按 user_idcreated_at 找订单,但还要返回 amountstatus,普通索引可能仍要回表。PostgreSQL 可以用:

CREATE INDEX idx_order_user_created
ON orders(user_id, created_at DESC)
INCLUDE (amount, status);

这样索引里既有查找键,又有返回列。满足可见性条件时,数据库可以不访问堆表,直接从索引返回数据。

INCLUDE 不是越多越好。附加列会增大索引体积,影响写入和缓存;而且 index-only scan 还依赖 visibility map,不是建了覆盖索引就必然不回表。

完整版教学

一、为什么需要覆盖索引

数据库通过索引定位行后,通常还要回到堆表取完整行。这个动作在 PostgreSQL 里尤其常见,因为普通 B-tree 索引项不保存整行数据。

如果查询只需要少量列,把这些列放进索引,就有机会减少堆表访问。对于高频列表页,这种优化很有价值。

例如“查询某用户最近 20 笔订单,只展示金额和状态”,不一定需要访问订单表的所有列。

二、INCLUDE 的语法和作用

INCLUDE 把列附加到索引中,但它们不是索引键:

CREATE INDEX idx_order_user_created
ON orders(user_id, created_at DESC)
INCLUDE (amount, status);

user_id, created_at 参与查找和排序;amount, status 主要用于覆盖 SELECT 返回列。这样比把所有列都放进联合索引键更清晰。

查询示例:

SELECT created_at, amount, status
FROM orders
WHERE user_id = 1001
ORDER BY created_at DESC
LIMIT 20;

三、和联合索引的区别

联合索引键会影响索引的排序结构和查询匹配顺序;INCLUDE 列只是附带存储在叶子项中,不用于决定索引顺序。

维度联合索引键INCLUDE 列
参与查找
参与排序
用于覆盖返回
影响键大小限制更敏感相对独立但仍占空间

如果列要参与 WHEREORDER BY、范围扫描,应放在索引键;如果只是返回展示,可以考虑 INCLUDE

四、index-only scan 还依赖可见性

PostgreSQL 的 MVCC 决定了索引项本身不一定能判断行对当前事务是否可见。只有 visibility map 标记某些堆页全可见时,index-only scan 才能真正少访问堆表。

这意味着刚被大量更新的表,即使有覆盖索引,也可能仍要访问堆表确认可见性。VACUUM 和表更新频率会影响效果。

数字例子:如果查询命中 1000 条索引项,其中 900 条所在页面已全可见,只需少量堆访问;如果几乎没有页面全可见,覆盖索引收益会变小。

五、不要把所有列都 INCLUDE

覆盖索引不是小型物化表。INCLUDE 列越多,索引越大,写入、更新、缓存和 VACUUM 成本越高。

尤其是宽字段、频繁更新字段不适合随便放进索引。更新 INCLUDE 列也可能导致索引维护,影响写入性能。

好的做法是只覆盖高频查询真正返回的少量稳定列。

六、常见误区与追问

  • 误区:INCLUDE 列也能加速过滤。 它主要用于覆盖返回,不参与索引查找顺序。
  • 误区:覆盖索引一定会 index-only scan。 PostgreSQL 还要看 visibility map 和可见性判断。
  • 误区:把所有 SELECT 列都 include 最好。 索引会变大,写入和缓存成本会上升。
  • 追问:哪些列适合 INCLUDE? 高频查询返回、较小、较稳定、不参与过滤排序的列。
  • 追问:如何验证效果?EXPLAIN ANALYZE 看是否出现 Index Only Scan 以及 Heap Fetches。

七、加强记忆

记忆钩子:联合索引键是目录排序规则,INCLUDE 列是夹在目录旁的小抄;小抄能少翻正文,但不能拿来决定目录怎么排。

回答这题时要把“覆盖返回列、区别于联合索引键、依赖 visibility map、控制索引体积”讲清楚。这样能体现 PostgreSQL 和 MySQL 覆盖索引语义的差异。