这是《PostgreSQL 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 B-Tree 是 PostgreSQL 默认索引,适合等值、范围和排序。HASH 索引只适合等值,适用面较窄。
8.1 B-Tree
CREATE INDEX idx_orders_user_created
ON orders(user_id, created_at DESC);
B-Tree 适合:
=;>、>=、<、<=;BETWEEN;IS NULL;ORDER BY;- 前缀
LIKE 'abc%'。
不适合:
LIKE '%abc';%abc%;- 对列套函数后查询;
- 类型不匹配导致隐式转换;
- 低选择性大范围扫描。
8.2 复合索引
CREATE INDEX idx_orders_status_created
ON orders(status, created_at);
有效:
WHERE status = 'PAID'
WHERE status = 'PAID'
AND created_at > now() - interval '1 day'
ORDER BY created_at
较弱:
WHERE created_at > now() - interval '1 day'
复合索引遵循左前缀原则。设计顺序通常是等值列在前、范围列在后。
8.3 覆盖索引
CREATE INDEX idx_orders_cover
ON orders(user_id, created_at)
INCLUDE (amount, status);
INCLUDE 列不参与排序,可用于 Index Only Scan。是否能只扫索引还取决于可见性映射和 vacuum 状态。
8.4 部分索引
只索引活跃订单:
CREATE INDEX idx_orders_open
ON orders(created_at)
WHERE status IN ('CREATED', 'PAID');
适合状态少部分占比、查询条件固定的场景。
8.5 表达式索引
CREATE INDEX idx_users_lower_email
ON users(lower(email));
查询必须使用相同表达式:
SELECT *
FROM users
WHERE lower(email) = 'alice@example.com';
适合函数值高频查询,但增加写入成本,并要求函数语义稳定。
8.6 唯一索引
CREATE UNIQUE INDEX uq_orders_order_no
ON orders(order_no);
部分唯一索引常配合软删除:
CREATE UNIQUE INDEX uq_users_email_active
ON users(email)
WHERE deleted_at IS NULL;
8.7 HASH 索引
CREATE INDEX idx_users_email_hash
ON users USING HASH (email);
| 维度 | HASH | B-Tree |
|---|---|---|
| 等值 | 支持 | 支持 |
| 范围排序 | 不支持 | 支持 |
| 索引大小 | 可能更小 | 通常更大 |
| 适用面 | 较窄 | 通用 |
只有纯等值、基数高且 B-Tree 空间压力大时才值得评估 HASH。
8.8 索引维护
查看索引使用:
SELECT
schemaname,
relname,
indexrelname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM pg_stat_user_indexes
ORDER BY idx_scan;
重建:
REINDEX INDEX idx_orders_user_created;
REINDEX TABLE CONCURRENTLY orders;
删除索引前观察完整业务周期,避免只看低峰一天。
8.9 索引代价
- 写入放大;
- 存储成本;
- 更新热点;
- VACUUM 负担;
- 优化器选择成本。
索引不是越多越好。每个索引都应能对应明确查询。
本章小结
B-Tree 覆盖大多数等值、范围和排序需求。复合索引注意左前缀,表达式和部分索引能解决特殊模式,HASH 只适合纯等值。索引设计要同时考虑查询收益和写入代价。
思考题
- 为什么复合索引左前缀重要?
- INCLUDE 列和键列有什么区别?
- 部分索引适合什么场景?
- 表达式索引有什么限制?
- 如何发现无用索引?