PostgreSQL 部分索引(Partial Index)详解

PostgreSQL 部分索引(Partial Index)详解

一、什么是部分索引?

普通索引会对表中所有行 建立索引条目;而部分索引只对满足特定条件的行建立索引。

sql 复制代码
-- 普通索引:对全表 10000 万行建索引
CREATE INDEX idx_normal ON mage_op_order_full (version_number, svc_tag);

-- 部分索引:只对"待处理的 ESS 行"建索引(可能只有几万行)
CREATE INDEX idx_partial ON mage_op_order_full (version_number, svc_tag)
WHERE ess_no IS NULL
  AND source_parent_id IS NOT NULL;

一句话理解:普通索引是给全班同学建花名册,部分索引是只给"还没交作业的同学"建花名册。


二、真实案例背景

在 Magellan ST 数据集成链路中,有一个 flush2 SQL 专门用于补填 ESS 产品编号:

sql 复制代码
-- flush2:找出 ESS 行中 ess_no 为空的记录,从父记录中回填 product_no
UPDATE mage_op_order_full a
SET ess_no = b.product_no
FROM mage_op_order_full b
WHERE a.version_number = #{versionNumber}   -- 当前批次
  AND a.svc_tag = 'ESS'                     -- 只处理 ESS 类型
  AND a.ess_no IS NULL              -- 尚未填充
  AND a.source_parent_id IS NOT NULL -- 有父记录
  AND a.source_parent_id = b.sell_through_record_id

表数据特征:

  • mage_op_order_full 总数据量:数千万行
  • 其中 svc_tag = 'ESS'ess_no IS NULL 的行:极少数(只有新批次刚写入的数据)
  • 随着 flush2 执行完毕,这些行的 ess_product_no 被填充后,自动退出部分索引范围

三、普通索引 vs 部分索引

3.1 索引大小对比

复制代码
表总行数:5000 万行
其中 ess_no IS NULL 的行:约 50 万行(仅占 1%)

普通索引大小:~2GB(覆盖 5000 万行)
部分索引大小:~20MB(只覆盖 50 万行)

索引体积缩小 100 倍 → 全部装入内存 → 查询速度极快

3.2 写入维护成本对比

操作 普通索引 部分索引
INSERT 新行(ess_no IS NULL) 必须写入索引 必须写入索引
INSERT 新行(ess_no 有值) 必须写入索引 ✅ 不需要维护
UPDATE 填充 ess_product_no(IS NULL → 有值) 索引条目需要删除+更新 ✅ 行退出索引范围,自动失效
UPDATE 与索引无关的字段 所有索引都要检查 ✅ 不在条件范围内的行直接跳过

核心优势 :只有 1% 的行满足部分索引条件,但 99% 的 INSERT/UPDATE 操作都不需要维护这个索引,写入成本降低 99%

3.3 查询命中条件对比

sql 复制代码
-- ✅ 能命中部分索引:查询条件包含部分索引的 WHERE 子句
UPDATE ... WHERE version_number = ?
              AND svc_tag = 'ESS'
              AND ess_no IS NULL        -- ← 包含此条件
              AND source_parent_id IS NOT NULL  -- ← 包含此条件

-- ❌ 不能命中部分索引:查询条件不满足部分索引的 WHERE 子句
SELECT * FROM mage_op_order_full WHERE version_number = ?
-- 这个查询没有 ess_no IS NULL 条件,走普通索引或全表扫描

四、建立部分索引

针对 flush2 的查询模式,建议创建如下部分索引:

sql 复制代码
-- 针对 flush2 的部分索引
CREATE INDEX idx_st_full_ess_flush
ON mage_op_order_full (version_number, svc_tag)
WHERE ess_no IS NULL
  AND source_parent_id IS NOT NULL;

索引字段解释:

  • version_number:WHERE 中的等值过滤,放第一位(选择性高)
  • svc_tag:WHERE 中的等值过滤 = 'ESS',放第二位
  • WHERE ess_no IS NULL AND source_parent_id IS NOT NULL:部分索引条件,只索引待处理的行

五、部分索引的"自动缩减"特性

这是部分索引最精妙的地方,结合本案例理解:

复制代码
时间线:

T1: 新批次写入 50 万条 ESS 行,ess_no = NULL
    → 这 50 万行进入部分索引
    → 索引大小:50 万条目

T2: flush2 执行,回填 ess_product_no
    → 每填充一行,ess_no IS NULL 不再成立
    → 该行自动退出部分索引范围
    → 索引大小:逐渐缩减至 0

T3: flush2 执行完毕
    → 部分索引几乎为空(只剩真正没有父记录的极少数行)
    → 索引几乎不占空间,也不影响后续写入性能

这就像"待办清单",完成一项划掉一项,清单越来越短,查找速度始终很快。


六、验证部分索引是否生效

sql 复制代码
-- 用 EXPLAIN ANALYZE 查看执行计划
EXPLAIN ANALYZE
UPDATE mage_op_order_full a
SET ess_no = b.product_no
FROM mage_op_order_full b
WHERE a.version_number = '20260522000707011'
  AND a.svc_tag = 'ESS'
  AND a.ess_no IS NULL
  AND a.source_parent_id IS NOT NULL
  AND a.source_parent_id = b.record_id;

执行计划中出现以下内容,说明部分索引已命中:

复制代码
Index Scan using idx_st_full_ess_flush on mage_op_order_full a
  Index Cond: ((version_number = '20260522000707011') AND (svc_tag = 'ESS'))
  Filter: (ess_no IS NULL AND source_parent_id IS NOT NULL)

七、什么时候适合用部分索引?

适用场景 说明 示例
只处理少数状态的数据 大表中只有少数行处于"待处理"状态 status = 'PENDING',处理完变为 DONE
软删除过滤 绝大多数行都是有效数据 WHERE deleted_at IS NULL
特定类型数据加速 只有某个枚举值的子集需要高频查询 WHERE svc_tag = 'ESS'
NULL 值过滤 表中大量字段为 NULL,只查非 NULL WHERE ess_no IS NULL
时间窗口查询 只查最近 N 天的数据 WHERE created_at > NOW() - INTERVAL '7 days'

八、部分索引 vs 普通索引 总结

对比维度 普通索引 部分索引
索引覆盖范围 全表所有行 只覆盖满足 WHERE 条件的行
索引体积 大(与表成比例) 小(只有目标行)
写入维护成本 每次 INSERT/UPDATE 都要维护 只有满足条件的行才维护
查询命中条件 只需查询字段匹配 查询条件必须包含索引的 WHERE 子句
适用场景 通用查询 特定状态/条件的高频查询
MySQL 支持 ❌(MySQL 不支持)
PostgreSQL 支持

九、注意事项

1. 查询条件必须"覆盖"部分索引条件

sql 复制代码
-- ✅ 命中:查询条件包含了部分索引的 WHERE 子句
WHERE version_number = ? AND svc_tag = 'ESS' AND ess_no IS NULL

-- ❌ 不命中:缺少 ess_no IS NULL 条件
WHERE version_number = ? AND svc_tag = 'ESS'

2. 新建部分索引需要 REINDEX 或等待 autovacuum

sql 复制代码
-- 建好后立即生效,不需要重建
-- 但如果表已有大量数据,建索引过程会扫描全表一次(只扫满足条件的行)
CREATE INDEX CONCURRENTLY idx_st_full_ess_flush  -- 加 CONCURRENTLY 不锁表
ON mage_op_order_full (version_number, svc_tag)
WHERE ess_no IS NULL AND source_parent_id IS NOT NULL;

3. 部分索引不适合频繁变化的条件列

如果 WHERE 条件中的列频繁被 UPDATE ,会导致行不断进出索引,反而增加维护开销。

本案例中 ess_no 只会从 NULL → 有值(单向变化),非常适合部分索引。


十、一句话总结

部分索引是"给少数特殊行开的快速通道",它比普通索引体积更小、维护成本更低、查询更快------前提是你的查询条件能精确描述这批"特殊行"是谁。

相关推荐
数据库小学妹5 分钟前
AI时代数据库怎么选?多模融合、数据统一存储与选型实战指南
数据库·人工智能·经验分享·ai
Albert Edison14 分钟前
【Redis】Centos7.9 安装 Redis 5 教程
数据库·redis·缓存
云计算磊哥@32 分钟前
运维开发宝典026-MySQL02数据库表操作
运维·数据库·运维开发
小二·1 小时前
Redis 内存溢出(OOM)排查与恢复实战
数据库·redis·bootstrap
pqk6V6Vep1 小时前
Redis 分布式锁进阶第一篇讲解
数据库·redis·分布式
giaz14n9X1 小时前
Redis 分布式锁进阶第六十一篇
数据库·redis·分布式
是一个Bug1 小时前
MongoDB:像搭积木一样存数据
数据库·mongodb
ULIi096kr1 小时前
MySQL解决Too many connections报错:连接数爆满排查、优化与永久解决方案
数据库·mysql·adb
SL-staff2 小时前
(一)数据源配置 —— JVS-Rules规则引擎 V2.5 操作说明介绍
数据库·jar·规则引擎·数据源·jvs-rules·api 接口·jvs低代码
摇滚侠3 小时前
Spring 零基础入门到进阶 基于 XML 管理 Bean 14-28
xml·数据库·spring