PostgreSQL 部分索引

5 阅读8分钟

一、导言

一个部分索引是建立在表的一个子集上的索引,该子集由条件表达式(称为部分索引的谓词)定义,索引中仅包含符合该谓词的表行数据。部分索引属于专用索引特性,在特定业务场景下能够显著提升数据库性能。

创建部分索引的语法:在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建立索引。

  1. 表结构定义

    1. CREATE TABLE access_log (
          url varchar,
          client_ip inet,
          ...
      );
      
  2. 部分索引创建

    1. 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');
      
  3. 索引有效性验证

    1. 可使用索引的查询(查询外部IP)

      • SELECT *
        FROM access_log
        WHERE url = '/index.html' AND client_ip = inet '212.78.10.32';
        
    2. 不可使用索引的查询(查询内网IP)

      • SELECT *
        FROM access_log
        WHERE url = '/index.html' AND client_ip = inet '192.168.100.23';
        

场景2:排除查询不感兴趣的值

针对表中存在少量高频访问数据、大量低频访问数据的场景,仅为高频访问数据创建部分索引,可提升核心业务查询效率。需注意,该方式会阻止通过索引访问“不感兴趣”的数据,因此需要结合业务场景充分测试。

订单表的未上账订单部分索引

假设订单表 orders 包含已上账和未上账订单,未上账订单占比小且为高频访问数据,仅为未上账订单创建索引。

  1. 部分索引创建

    1. CREATE INDEX orders_unbilled_index ON orders (order_nr)
          WHERE billed is not true;
      
  2. 索引使用场景

    1. 精准匹配索引的查询

      • SELECT * FROM orders WHERE billed is not true AND order_nr < 10000;
        
    2. 扫描全量索引的查询(虽无 order_nr 条件,但未上账订单量小,仍有性能优势)

      • SELECT * FROM orders WHERE billed is not true AND amount > 5000.00;
        
    3. 不可使用索引的查询(无法确定订单是否未上账)

      • SELECT * FROM orders WHERE order_nr = 3501;
        

场景3:实现子集数据的唯一性约束

部分索引可在表的子集上创建唯一索引,强制满足谓词条件的数据的唯一性,对不满足条件的数据无约束作用。

测试结果表的唯一成功记录约束

假设测试结果表 tests 需保证同一主题和目标组合仅存在一条成功记录,不成功记录无唯一性限制。

  1. 表结构定义

    1. CREATE TABLE tests (
          subject text,
          target text,
          success boolean,
          ...
      );
      
  2. 部分唯一索引创建

    1. CREATE UNIQUE INDEX tests_success_constraint ON tests (subject, target)
          WHERE success;
      

场景4:优化查询规划选择

当数据集分布特殊,导致PostgreSQL查询规划器做出不合理的索引选择时,可通过部分索引过滤无关查询,避免无效索引扫描。

注意:PostgreSQL规划器通常能做出合理的索引选择,此场景为特殊优化手段,需谨慎使用。

三、部分索引的关键技术要点

  1. 谓词匹配规则

    1. 索引列与谓词列无需完全匹配,PostgreSQL支持使用索引表任意列的谓词表达式。
    2. 查询的 WHERE 条件需数学蕴含索引谓词,规划器才能识别并使用部分索引。
    3. 规划器仅支持简单的蕴含关系识别(如 x < 1 蕴含 x < 2),无法识别复杂的等价表达式;且匹配发生在查询规划阶段,参数化查询(如 x < ?)无法适配部分索引。
    4. 谓词条件书写规范:① 仅能引用当前索引表的列,不可引用其他表的列或子查询;② 支持常见逻辑运算符(AND、OR、NOT)、比较运算符(=、<、>、<=、>=、<>)及符合PostgreSQL语法的函数(如inet相关函数);③ 避免使用不确定函数(如now()、random()),此类函数会导致索引谓词条件动态变化,无法稳定筛选数据子集;④ 条件表达式需简洁明确,尽量与业务查询的WHERE条件形式一致,减少规划器的匹配难度;⑤ 若使用多条件组合,建议通过括号明确逻辑优先级,避免歧义(如示例11.1中通过括号明确IP段的范围判断逻辑)。
  2. 禁用场景:替代分区表的多部分索引

  1. 避免创建一组不重叠的部分索引来替代分区表,例如按类别创建多个索引:

    -- 不推荐的写法
    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的高性能优化工具,核心优势是减小索引体积、提升核心业务查询效率、降低更新开销,适用于公值明确、数据分布稳定、高频访问数据子集明确的场景。使用时需结合业务需求充分验证,避免滥用;在数据量极大的场景下,优先选择分区表而非多部分索引。