INCLUDE 覆盖列是什么
INCLUDE 子句把「只用来返回、不参与过滤和排序」的列挂到索引上,让查询不回表就能拿齐所需字段,也就是覆盖索引。PostgreSQL 11 起 B 树索引支持 INCLUDE;与把列直接放进键里相比,附加列不参与键的顺序与去重,索引上层节点不会被撑大,唯一索引的语义也不会被改变。
基本信息
| 支持范围 | PostgreSQL 11 起的 B 树索引;其他索引类型是否支持要按版本查手册 |
|---|---|
| 语法 | CREATE INDEX ... ON 表 (键列) INCLUDE (附加列) |
| 与键列区别 | INCLUDE 列不参与定位、排序与去重,只随索引行一起存储 |
| 收益 | 满足仅索引扫描,省掉回表的随机读 |
| 代价 | 索引体积变大,写入与日常维护成本随之上升 |
典型使用场景
- 高频点查带少量返回列:按 user_id 取最近订单并带出金额
- 列表分页:键列顺序固定,附加列用 INCLUDE 带出
- 保持唯一索引语义:唯一性只由键列决定,附加列放 INCLUDE 不破坏约束
- 避免键列臃肿:把长字符串列从键挪到 INCLUDE,上层节点更紧凑
- 替代加宽复合索引:某列不参与排序时,放进 INCLUDE 更贴合语义
- 热点表优化:减少回表随机读,缓解磁盘 IO 压力
建索引与验证
建索引与验证
CREATE INDEX idx_orders_user_created
ON orders (user_id, created_at) # 键列:过滤与排序都靠它
INCLUDE (total); # 附加列:只用来返回
EXPLAIN (ANALYZE, BUFFERS)
SELECT user_id, created_at, total
FROM orders
WHERE user_id = 100
ORDER BY created_at DESC
LIMIT 10; # 关注是否出现 Index Only Scan
VACUUM (ANALYZE) orders; # 回表次数偏高时先清理,刷新可见性信息计划逐项读法
| 计划项 | 含义 | 怎么判断 |
|---|---|---|
| Index Only Scan | 只读索引、不回表 | 出现即说明附加列起了作用 |
| Heap Fetches | 因可见性不足被迫回表的次数 | 接近 0 才算真正省下 IO,偏高先做清理 |
| Index Cond | 用于索引定位的条件 | INCLUDE 列不会出现在这一项里 |
| Filter | 在索引行上再逐行过滤的条件 | 把它挪进键列才能减少扫描的索引行 |
| Rows Removed by Filter | 被过滤掉的行数 | 数值很大说明条件没走索引定位 |
INCLUDE 列不能用于定位:WHERE 里需要过滤的列若放进 INCLUDE,条件依然退化成逐行过滤,只有列进键列才会真正减少扫描的索引行。判断标准就一条——它出现在 Index Cond 还是 Filter 里。
仅索引扫描到底省没省 IO,要看 Heap Fetches 和表的可见性信息:刚大批写入或长期未清理的表,页面没被标记为全部可见,计划写着仅索引扫描,执行时仍在悄悄回表。规律的清理(含自动清理)是拿到收益的前提。
索引行本身有大小限制,大约只能占页面的三分之一;很宽的值放进 INCLUDE 可能导致创建失败。遇到这类报错要考虑换成更窄的键,或干脆精简查询要返回的列。