PostgreSQL中WHERE func(col)无法使用普通索引,因为B-tree索引存储列原始值,而函数条件需匹配计算结果;必须创建与WHERE表达式完全一致的IMMUTABLE函数表达式索引才能生效。
WHERE func(col) 为什么走不了普通索引因为普通 B-tree 索引存储的是列的原始值,而 WHERE upper(name) = 'ABC' 这类条件需要匹配函数计算后的结果。优化器无法直接用原始值索引去查转换后的值,除非你告诉它“我经常这么查”,并提前把 upper(name) 的结果存成索引项。
语法很简单,但括号和函数调用必须完全一致——索引表达式要和 WHERE 中的表

CREATE INDEX idx_users_upper_name ON users (upper(name));
SELECT * FROM users WHERE upper(name) = 'JOHN';
CREATE INDEX idx_users_upper_name ON users (UPPER(name));(大写函数名在某些老版本可能不等价)WHERE UPPER(name) = 'JOHN' 对应小写 upper() 索引,可能不被选中(取决于规划器和版本)PostgreSQL 要求索引表达式必须是 IMMUTABLE 函数,即:给定相同输入,永远返回相同输出,且不依赖数据库状态(如当前时间、session 设置、locale)。否则无法保证索引一致性。
lower()、upper()、trim()、substring()、COALESCE(col, '')
now()、current_date、pg_backend_pid()、to_char(created_at, 'YYYY')(格式化受 locale 影响)regexp_replace() 默认是 STABLE,需显式声明 IMMUTABLE 才能建表达式索引(不推荐自行 cast,风险高)表达式索引不是银弹。它会额外占用磁盘空间,写入时多一次函数计算+索引更新,且只对严格匹配该表达式的查询生效(比如 upper(name) 索引对 WHERE name ILIKE 'abc%' 没用)。
WHERE substr(phone, 1, 3) = '138',不如加个生成列 + 普通索引更清晰EXPLAIN 看是否出现 Index Scan using idx_users_upper_name;若没命中,检查表达式是否真的一致,或尝试 SET enable_seqscan = off 强制测试最常被忽略的一点:表达式索引不继承父表定义,分区表每个子表得单独建;而且 VACUUM 和 ANALYZE 都要覆盖到它——不然统计信息不准,规划器照样不用。