一、导言
一个部分索引是建立在表的一个子集上的索引,该子集由条件表达式(称为部分索引的谓词)定义,索引中仅包含符合该谓词的表行数据。部分索引属于专用索引特性,在特定业务场景下能够显著提升数据库性能。
创建部分索引的语法:在PostgreSQL中,创建部分索引的核心语法在普通索引创建语句基础上增加WHERE子句(即部分索引谓词),具体格式如下:
CREATE [ UNIQUE ] INDEX [ 索引名称 ]
ON [ 表名 ] ( [ 索引列1 ], [ 索引列2 ], ... )
WHERE [ 谓词条件 ];
语法说明:
- UNIQUE:可选参数,用于创建部分唯一索引,仅对满足谓词条件的行强制唯一性约束(如示例11.3所示);
- 索引名称:自定义的索引标识,建议遵循“表名_列名_ix”的命名规范,便于识别;
- 表名:需要创建索引的目标表名称;
- 索引列:用于构建索引的列,可指定单个或多个列(联合部分索引);
- 谓词条件:用于筛选表子集的条件表达式,仅包含符合该条件的行会被纳入索引,支持表中任意列的逻辑判断,需确保条件明确且可提前预判。
二、核心应用场景、适应范围与示例
部分索引的应用场景需围绕“数据子集有明确区分特征、核心业务仅聚焦该子集”展开,不同场景的适用条件、优势及局限性存在差异,具体分类及分析如下:
| 应用场景 | 适用条件 | 核心优势 | 局限性 |
|---|---|---|---|
| 排除公值,减小索引体积 | 1. 表中存在占比极高的公值数据;2. 核心查询极少涉及公值;3. 数据分布稳定,公值可提前预判 | 1. 大幅缩减索引体积,降低存储开销;2. 提升非公值查询的索引扫描效率;3. 减少表更新时的索引维护成本(公值数据无需更新索引) | 1. 需精准预判公值范围,否则可能导致索引失效;2. 数据分布变化时需重建索引,增加维护负担 |
| 排除查询不感兴趣的值 | 1. 表中存在少量高频访问的“核心数据子集”和大量低频访问的“非核心数据”;2. 核心业务查询仅聚焦核心数据子集 | 1. 索引仅覆盖高频数据,查询效率优于全表索引;2. 降低索引维护成本,仅核心数据更新时触发索引更新 | 1. 阻止通过该索引访问非核心数据,需为非核心数据查询单独评估索引策略;2. 需充分测试核心数据子集的占比,占比过高时优势不明显 |
| 实现子集数据的唯一性约束 | 1. 仅需对表中部分数据强制唯一性(如“成功记录唯一,失败记录不限制”);2. 唯一性约束条件可通过谓词明确界定 | 1. 精准满足业务层面的差异化唯一性需求;2. 相比全表唯一索引,体积更小、维护成本更低 | 1. 谓词条件需与唯一性约束逻辑严格匹配,否则可能出现业务漏洞;2. 不满足谓词的数据无唯一性约束,需通过其他方式保障数据合法性 |
| 优化查询规划选择 | 1. 数据集分布特殊,导致查询规划器做出不合理的索引选择;2. 可通过谓词精准过滤无关查询,避免无效索引扫描 | 1. 纠正不合理的查询规划,提升核心查询效率;2. 无需修改查询语句,仅通过索引优化即可解决规划问题 | 1. 仅适用于特殊数据分布场景,通用性差;2. 对用户的索引原理和查询规划知识要求较高,易误用 |
场景1:排除公值,减小索引体积
搜索公值(在表中占比超过一定比例的值)的查询通常不会使用索引,因此将公值行排除在索引外,可有效减小索引尺寸、加速索引类查询和表更新操作。
适用前提:公值可提前预判,且数据分布相对稳定;若数据分布变化频繁,需重建索引以适配新分布,会增加维护成本。
网页服务器访问日志的部分索引
假设存在存储网页服务器访问日志的表 access_log,大部分访问来自内网IP段 192.168.100.0 - 192.168.100.255,业务需求主要是搜索外部IP的访问记录,因此无需为内网IP建立索引。
-
表结构定义
-
CREATE TABLE access_log ( url varchar, client_ip inet, ... );
-
-
部分索引创建
-
CREATE INDEX access_log_client_ip_ix ON access_log (client_ip) WHERE NOT (client_ip > inet '192.168.100.0' AND client_ip < inet '192.168.100.255');
-
-
索引有效性验证
-
可使用索引的查询(查询外部IP)
-
SELECT * FROM access_log WHERE url = '/index.html' AND client_ip = inet '212.78.10.32';
-
-
不可使用索引的查询(查询内网IP)
-
SELECT * FROM access_log WHERE url = '/index.html' AND client_ip = inet '192.168.100.23';
-
-
场景2:排除查询不感兴趣的值
针对表中存在少量高频访问数据、大量低频访问数据的场景,仅为高频访问数据创建部分索引,可提升核心业务查询效率。需注意,该方式会阻止通过索引访问“不感兴趣”的数据,因此需要结合业务场景充分测试。
订单表的未上账订单部分索引
假设订单表 orders 包含已上账和未上账订单,未上账订单占比小且为高频访问数据,仅为未上账订单创建索引。
-
部分索引创建
-
CREATE INDEX orders_unbilled_index ON orders (order_nr) WHERE billed is not true;
-
-
索引使用场景
-
精准匹配索引的查询
-
SELECT * FROM orders WHERE billed is not true AND order_nr < 10000;
-
-
扫描全量索引的查询(虽无
order_nr条件,但未上账订单量小,仍有性能优势)-
SELECT * FROM orders WHERE billed is not true AND amount > 5000.00;
-
-
不可使用索引的查询(无法确定订单是否未上账)
-
SELECT * FROM orders WHERE order_nr = 3501;
-
-
场景3:实现子集数据的唯一性约束
部分索引可在表的子集上创建唯一索引,强制满足谓词条件的数据的唯一性,对不满足条件的数据无约束作用。
测试结果表的唯一成功记录约束
假设测试结果表 tests 需保证同一主题和目标组合仅存在一条成功记录,不成功记录无唯一性限制。
-
表结构定义
-
CREATE TABLE tests ( subject text, target text, success boolean, ... );
-
-
部分唯一索引创建
-
CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target) WHERE success;
-
场景4:优化查询规划选择
当数据集分布特殊,导致PostgreSQL查询规划器做出不合理的索引选择时,可通过部分索引过滤无关查询,避免无效索引扫描。
注意:PostgreSQL规划器通常能做出合理的索引选择,此场景为特殊优化手段,需谨慎使用。
三、部分索引的关键技术要点
-
谓词匹配规则
- 索引列与谓词列无需完全匹配,PostgreSQL支持使用索引表任意列的谓词表达式。
- 查询的
WHERE条件需数学蕴含索引谓词,规划器才能识别并使用部分索引。 - 规划器仅支持简单的蕴含关系识别(如
x < 1蕴含x < 2),无法识别复杂的等价表达式;且匹配发生在查询规划阶段,参数化查询(如x < ?)无法适配部分索引。 - 谓词条件书写规范:① 仅能引用当前索引表的列,不可引用其他表的列或子查询;② 支持常见逻辑运算符(AND、OR、NOT)、比较运算符(=、<、>、<=、>=、<>)及符合PostgreSQL语法的函数(如inet相关函数);③ 避免使用不确定函数(如now()、random()),此类函数会导致索引谓词条件动态变化,无法稳定筛选数据子集;④ 条件表达式需简洁明确,尽量与业务查询的WHERE条件形式一致,减少规划器的匹配难度;⑤ 若使用多条件组合,建议通过括号明确逻辑优先级,避免歧义(如示例11.1中通过括号明确IP段的范围判断逻辑)。
-
禁用场景:替代分区表的多部分索引
-
避免创建一组不重叠的部分索引来替代分区表,例如按类别创建多个索引:
-- 不推荐的写法 CREATE INDEX mytable_cat_1 ON mytable (data) WHERE category = 1; CREATE INDEX mytable_cat_2 ON mytable (data) WHERE category = 2; ...该方式会导致规划器逐个测试索引是否适用,产生大量额外开销。
推荐方案
- 若数据量适中,创建联合索引:
sql CREATE INDEX mytable_cat_data ON mytable (category, data);
- 若数据量极大,使用分区表(参考第5.11节),规划器可识别分区的不重叠特性,实现更优性能。
四、总结
部分索引是PostgreSQL的高性能优化工具,核心优势是减小索引体积、提升核心业务查询效率、降低更新开销,适用于公值明确、数据分布稳定、高频访问数据子集明确的场景。使用时需结合业务需求充分验证,避免滥用;在数据量极大的场景下,优先选择分区表而非多部分索引。