PostgreSQL INCLUDE 列 vs 普通索引列的区别

📅 2026/7/3 8:44:53 👁️ 阅读次数 📝 编程学习
PostgreSQL INCLUDE 列 vs 普通索引列的区别

PostgreSQL:INCLUDE 列 vs 普通索引列的区别


对比示例

-- 方案一:全部放普通索引列CREATEINDEXidx_fullONorders(status,region,created_atDESC,amount,order_no);-- 方案二:使用 INCLUDE 列CREATEINDEXidx_includeONorders(status,region,created_atDESC)INCLUDE(amount,order_no);

核心区别一览

对比点普通索引列(a, b, c, d, e)INCLUDE(a, b, c) INCLUDE (d, e)
参与 B-Tree 排序✅ 全部参与❌ INCLUDE 列不参与
用于 WHERE 条件定位✅ 可以❌ 不能用于索引定位
用于 ORDER BY 走索引✅ 可以❌ 不能
覆盖索引(避免回表)✅ 可以✅ 可以
索引体积较大相对较小(INCLUDE 列只存叶子节点)
唯一索引约束范围约束所有列的组合唯一性只看非 INCLUDE 列

详细说明

方案一:全部放普通索引列

CREATEINDEXidx_fullONorders(status,region,created_atDESC,amount,order_no);

B-Tree 排序结构:

status → 第一层排序 region → 第二层排序(status 相同时) created_at → 第三层排序(前两列相同时) amount → 第四层排序(前三列相同时) order_no → 第五层排序(前四列相同时)

问题:

对于以下 SQL,amountorder_no参与了 B-Tree 的排序结构,增加了索引体积和写入成本,但对排序和过滤没有额外的加速效果。

SELECTstatus,region,created_at,amount,order_noFROMordersWHEREstatus='active'ANDregion='AP'ORDERBYcreated_atDESCLIMIT20;

方案二:使用 INCLUDE 列

CREATEINDEXidx_includeONorders(status,region,created_atDESC)INCLUDE(amount,order_no);

索引内部结构:

内部节点(导航用): status、region、created_at 参与 B-Tree 排序 叶子节点(存储数据): status、region、created_at、amount(附带)、order_no(附带)

效果:

  • statusregion→ 等值定位 ✅
  • created_at→ 利用索引顺序完成排序 ✅
  • amountorder_no→ 只存在叶子节点,SELECT 时直接读取,避免回表
  • 索引体积更小,写入成本更低 ✅

执行计划对比

没有 INCLUDE(需要回表)

Index Scan using idx_full on orders Index Cond: (status = 'active' AND region = 'AP') → 需要回表读取 amount、order_no

有 INCLUDE(Index Only Scan,不回表)

Index Only Scan using idx_include on orders Index Cond: (status = 'active' AND region = 'AP') → amount、order_no 直接从索引叶子节点读取,无需回表 ✅

什么时候用 INCLUDE?

典型场景:SELECT 字段多,但 WHERE / ORDER BY 用不到某些字段

-- 查询场景SELECTorder_no,amountFROMordersWHEREstatus='active'ANDregion='AP'ORDERBYcreated_atDESC;
-- 推荐索引:WHERE/ORDER BY 字段放普通列,SELECT 多余字段放 INCLUDECREATEINDEXidx_bestONorders(status,region,created_atDESC)INCLUDE(order_no,amount);

唯一索引场景的重要区别

-- 唯一性约束覆盖 status + region + created_at + amount + order_noCREATEUNIQUEINDEXidx_unique_fullONorders(status,region,created_atDESC,amount,order_no);-- 唯一性约束只覆盖 status + region + created_at-- amount、order_no 不参与唯一性判断CREATEUNIQUEINDEXidx_unique_includeONorders(status,region,created_atDESC)INCLUDE(amount,order_no);

验证索引效果

-- 查看是否走了 Index Only Scan(不回表)EXPLAIN(ANALYZE,BUFFERS)SELECTorder_no,amountFROMordersWHEREstatus='active'ANDregion='AP'ORDERBYcreated_atDESCLIMIT20;

关注点:

Index Only Scan → INCLUDE 生效,不回表 ✅ Index Scan → 有回表,可考虑加 INCLUDE ⚠️ Seq Scan → 索引未被使用 ❌

一句话总结

普通索引列决定数据如何排序和定位(影响索引结构);
INCLUDE 列只是把数据"夹带"进叶子节点,让查询不用回表,但不参与任何排序和过滤逻辑。

普通索引列 → 影响索引结构(排序、定位、唯一性约束) INCLUDE 列 → 只存值,用于覆盖查询避免回表,不影响索引结构