PostgreSQL 覆盖索引 INCLUDE 是什么?和联合索引有什么区别?
简化版
INCLUDE 可以把非搜索条件列附加到索引叶子中,让查询有机会做 index-only scan。它和联合索引的区别是:联合索引键参与排序和查找,INCLUDE 列主要用于覆盖返回字段,不参与索引查找顺序。
详细版
如果查询按 user_id 和 created_at 找订单,但还要返回 amount、status,普通索引可能仍要回表。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 列 |
|---|---|---|
| 参与查找 | 是 | 否 |
| 参与排序 | 是 | 否 |
| 用于覆盖返回 | 是 | 是 |
| 影响键大小限制 | 更敏感 | 相对独立但仍占空间 |
如果列要参与 WHERE、ORDER 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 覆盖索引语义的差异。