PostgreSQL中WHERE func(col)无法使用普通索引,因为B-tree索引存储列原始值,而函数条件需匹配计算结果;必须创建与WHERE表达式完全一致的IMMUTABLE函数表达式索引才能生效。
PostgreSQL 中
为什么走不了普通索引
因为普通 B-tree 索引存储的是列的原始值,而
这类条件需要匹配函数计算后的结果。优化器无法直接用原始值索引去查转换后的值,除非你告诉它“我经常这么查”,并提前把
的结果存成索引项。
创建表达式索引的正确写法
语法很简单,但括号和函数调用必须完全一致——索引表达式要和
中的表达式字面量一模一样,包括大小写、空格(虽然 PostgreSQL 通常忽略空格,但为保险起见建议保持一致)。
✅ 正确:
✅ 查询能命中:
❌ 错误:
(大写函数名在某些老版本可能不等价)
❌ 不匹配:
对应小写
索引,可能不被选中(取决于规划器和版本)
哪些函数支持表达式索引?关键看是否“immutable”
PostgreSQL 要求索引表达式必须是
函数,即:给定相同输入,永远返回相同输出,且不依赖数据库状态(如当前时间、
session
设置、locale)。否则无法保证索引一致性。
判断邮箱是否合法类
判断邮箱是否合法类
下载
✅ 安全:
、
、
、
、
❌ 禁止:
、
、
、
(格式化受 locale 影响)
⚠️ 注意:
默认是
,需显式声明
才能建表达式索引(不推荐自行 cast,风险高)
性能与维护成本的真实权衡
表达式索引不是银弹。它会额外占用磁盘空间,写入时多一次函数计算+索引更新,且只对严格匹配该表达式的查询生效(比如
索引对
没用)。
适合场景:高频、固定模式的函数过滤,如统一转大写查用户名、提取
邮箱
域名做分片键
避免滥用:如果只是偶尔
,不如加个生成列 + 普通索引更清晰
调试技巧:用
看是否出现
;若没命中,检查表达式是否真的一致,或尝试
强制测试
最常被忽略的一点:表达式索引不继承父表定义,分区表每个子表得单独建;而且
和
都要覆盖到它——不然统计信息不准,规划器照样不用。
WHERE func(col)WHERE upper(name) = 'ABC'upper(name)WHERECREATE 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()IMMUTABLElower()upper()trim()substring()COALESCE(col, '')now()current_datepg_backend_pid()to_char(created_at, 'YYYY')regexp_replace()STABLEIMMUTABLEupper(name)WHERE name ILIKE 'abc%'WHERE substr(phone, 1, 3) = '138'EXPLAINIndex Scan using idx_users_upper_nameSET enable_seqscan = offVACUUMANALYZE