SQL 函数索引 vs 表达式索引 vs 部分索引的生产落地对比

13次阅读

postgresql 中 create index on t ((lower(name))) 是表达式索引,非独立“函数索引”;其命中要求查询中表达式字面完全一致(含大小写、括号),且 ilike 等无法自动利用,需改写为 lower(name) = lower(‘alice’)。

SQL 函数索引 vs 表达式索引 vs 部分索引的生产落地对比

PostgreSQL 里 CREATE INDEX ON t ((lower(name))) 是表达式索引,不是函数索引

很多人看到 lower() 就以为是“函数索引”,其实 PostgreSQL 没有独立的“函数索引”类型——所有基于函数调用的索引都属于表达式索引。关键区别在于:表达式索引要求查询条件中出现 ** 完全一致的表达式 ** 才能命中,大小写、括号、空格都不能差。

常见错误现象:WHERE lower(name) = 'alice' 能走索引,但 WHERE name ILIKE 'alice'WHERE LOWER(name) = 'alice'(大写函数名)在某些旧版本可能不匹配(取决于规划器对函数大小写的敏感度)。

  • 必须确保查询中表达式与索引定义 ** 字面完全一致 **,包括函数名大小写、嵌套层级、是否加括号
  • ILIKE 不会自动利用 lower() 表达式索引,得改写成 lower(name) = lower('Alice')
  • 如果字段常做模糊前缀查(如 name LIKE 'ali%'),表达式索引没用,该上 text_pattern_opspg_trgm

部分索引 WHERE status = 'active' 的生效前提是 WHERE 条件能静态推导出谓词

部分索引只在查询条件能被规划器 ** 确定性地等价推出 ** 索引谓词时才启用。它不是“只要 WHERE 里写了 status = 'active' 就一定用”,而是看整个查询逻辑能否被证明满足索引约束。

使用场景:用户表里 95% 是 inactive,但活跃用户查询极频繁;订单表中只有 state IN ('paid', 'shipped') 的记录需要高频检索。

  • 带参数的查询(如 WHERE status = $1)在 prepare 阶段无法推导,可能跳过部分索引,除非绑定具体值后重计划
  • 复合条件如 WHERE status = 'active' AND created_at > '2024-01-01' 仍可用,但若写成 WHERE status IN ('active', 'pending'),哪怕只查 active,索引也不会用
  • 部分索引不降低写入开销——插入时仍要检查是否满足 WHERE 条件,不满足就跳过索引更新,但判断本身有微量 CPU 成本

三者共存时,PostgreSQL 选哪个索引?看执行计划,别猜

没有“优先级排序”。优化器根据统计信息估算代价,从所有可用索引(包括表达式、部分、普通 B-tree)里挑它认为最便宜的那个。同一个查询,在不同数据分布下可能选完全不同索引。

容易踩的坑:本地小数据集上 EXPLAIN 看着走了部分索引,上线后因数据倾斜或统计信息过期,实际跑的是全表扫描。

  • 务必在生产数据量级 + VACUUM ANALYZE 后再看 EXPLAIN (ANALYZE, BUFFERS)
  • 不要同时建 INDEX ON t (status)INDEX ON t (status) WHERE status = 'active'——后者更精准,前者几乎没存在价值,还拖慢 INSERT/UPDATE
  • 表达式索引和部分索引可以叠加:CREATE INDEX ON t ((lower(email))) WHERE verified = true,但维护成本和命中门槛同步升高

MySQL 和 SQLite 的“函数索引”根本不是一回事

MySQL 8.0+ 的函数索引是语法糖,底层仍是表达式索引,但允许你写 INDEX (lower(name)) 这种形式;SQLite 3.30+ 的表达式索引也类似。但二者都不支持 PostgreSQL 那种灵活的 WHERE 子句过滤(即没有部分索引能力)。

兼容性影响明显:想把 PostgreSQL 的 CREATE INDEX ON t ((lower(name))) WHERE deleted_at IS NULL 迁到 MySQL,得拆成两步——先建函数索引,再靠应用层或触发器保证 deleted_at IS NULL 的数据隔离。

  • MySQL 函数索引要求表达式必须是“确定性”的(比如不能含 NOW()RAND()),否则建索引直接报错 ERROR 3719
  • SQLite 表达式索引不支持 COLLATE 指定,大小写处理不如 PostgreSQL 灵活
  • 跨数据库迁移时,最容易被忽略的是部分索引的语义缺失——它不是性能选项,而是数据子集的物理划分方式
实际用哪一种,取决于你查什么、删什么、写多快、数据多歪。表达式索引锁住写法,部分索引锁住数据子集,两者叠一起,边界就特别硬。硬边界在测试里不显眼,一到线上数据长胖、查询变味,就露馅。

text=ZqhQzanResources