在PostgreSQL中,当查询条件对列使用了函数或表达式运算时,常规建立在列上的B树索引通常无法被使用,数据库只能进行全表扫描并逐行计算。表达式索引(Expression Index)正是为解决这一问题而生,它将对列的表达式计算结果作为索引键存储,使包含相同表达式的查询可以直接通过索引定位数据。

什么是表达式索引
表达式索引是指使用表列的函数结果、算术运算结果或任意不可变表达式作为索引键所创建的索引。它与普通索引的区别在于,普通索引直接引用列名,而表达式索引引用的是类似lower(name)或price * quantity这样的计算式。PostgreSQL要求表达式索引中使用的函数必须是IMMUTABLE属性,也就是在相同输入下永远返回相同输出,不依赖外部状态或当前时间。
之所以强调不可变,是因为索引在创建时和后续每次数据写入时都会计算表达式的值并持久化。如果函数值可能变化,索引就会和表数据不一致。例如now()是易变函数,不能用于表达式索引;而lower(text)只依赖输入参数,属于稳定的不可变函数,可以放心使用。
如何创建表达式索引
创建语法非常直观,只需要在CREATE INDEX时把列名替换为表达式。下面以用户表为例,我们经常按照小写的邮箱进行查询:
-- 创建用户表
CREATE TABLE app_user (
id serial PRIMARY KEY,
email text
);
-- 插入示例数据
INSERT INTO app_user (email) VALUES
('Tom@ippipp.com'),
('Alice@ipipp.com'),
('Bob@ipipp.com');
-- 创建基于 lower(email) 的表达式索引
CREATE INDEX idx_user_email_lower ON app_user (lower(email));
创建完成后,当执行WHERE lower(email) = 'tom@ipipp.com'时,规划器会识别到索引键与查询表达式完全一致,从而选择索引扫描。要注意的是,查询中的表达式必须和索引定义逐字符匹配,包括函数名大小写与参数顺序。如果索引是lower(email),而查询写成lower(email::text)或lcase(email)(假设存在),都无法命中。
除了单一函数,表达式索引也支持多列组合运算。例如对订单表同时按买家ID和金额税后值查询,可以建立(buyer_id, amount * 1.13)的复合表达式索引。但要注意复合索引的前缀匹配原则依然适用,若查询未携带buyer_id条件,则索引后半部分难以利用。
性能对比与实践验证
我们用一张十万行的测试表来观察差异。表中有name列且混合大小写,频繁执行不区分大小写的搜索。未建表达式索引前,执行计划为顺序扫描;建立之后变为索引扫描。以下为验证脚本:
-- 生成测试数据 CREATE TABLE person (id int, name text); INSERT INTO person SELECT g, 'User' || g || CASE WHEN g % 2 = 0 THEN '_A' ELSE '_b' END FROM generate_series(1, 100000) g; -- 无表达式索引的查询 EXPLAIN ANALYZE SELECT * FROM person WHERE lower(name) = 'user500_b'; -- 建立表达式索引 CREATE INDEX idx_person_name_lower ON person (lower(name)); -- 有表达式索引的查询 EXPLAIN ANALYZE SELECT * FROM person WHERE lower(name) = 'user500_b';
在多数环境中,全表扫描耗时在几十毫秒级别并随数据量线性增长,而表达式索引可将查询压缩到亚毫秒或一两毫秒。其本质是把运行时逐行计算下推到写入时一次性计算,并借助B树的有序性做二分查找。对于读多写少、函数查询频繁的场景,收益极为明显。
不过表达式索引并非免费午餐。每次INSERT或UPDATE涉及索引列时,数据库都要额外计算表达式并维护索引结构,写吞吐会下降。此外索引体积通常大于单列索引,因为存储的是计算结果。因此应针对真正高频的查询条件建立,而非盲目覆盖所有可能函数。
常见误区与注意事项
一个典型误区是认为只要查询用了函数就一定能走表达式索引。实际上索引表达式必须与查询中的表达式文本一致。例如索引是lower(name),但查询写成lower(trim(name)),规划器不会自动拆解函数,依旧走全表扫描。此时需要建立lower(trim(name))的新索引。
另一个注意点是表达式索引对ORDER BY同样有效。如果业务常按upper(title)排序,建立对应表达式索引可避免排序步骤。同时可以利用pg_indexes视图定期检查冗余索引,防止多个相似表达式索引占用过多空间。合理使用表达式索引,能够让PostgreSQL在函数查询场景下保持高性能。
PostgreSQL表达式索引函数查询修改时间:2026-08-11 12:15:33