函数索引是主流数据库加速表达式查询的机制,通过物化并索引函数结果(如UPPER(name))实现;创建时需确保表达式完全一致、函数为immutable且稳定,建完须用EXPLAIN验证命中。

函数索引(Function Index)是 PostgreSQL 8.4+ 和 Oracle、SQL Server(计算列+索引)、MySQL 8.0+ 等主流数据库支持的机制,专门用来加速对「表达式结果」的查询——比如 UPPER(name)、DATE(created_at) 或 jsonb_path_query_first(data, '$.title') 这类被函数包裹的列。它不是“给函数建索引”,而是把函数调用的结果物化并索引起来。
PostgreSQL 中创建函数索引的实际写法
最常见场景:想按大小写不敏感查用户名,但又不想改应用逻辑或加冗余字段。
错误做法是写 WHERE UPPER(name) = 'ALICE' 却没索引,导致全表扫描;正确做法是显式创建函数索引:
CREATE INDEX idx_users_name_upper ON users (UPPER(name));
之后这个查询就能走索引:
SELECT * FROM users WHERE UPPER(name) = 'ALICE';
- 索引表达式必须和查询中使用的表达式**完全一致**(包括括号、空格、函数参数顺序),否则优化器不会匹配
- PostgreSQL 要求表达式是「immutable」(确定性)的,
UPPER()是,但NOW()、CURRENT_USER就不行 - 如果经常同时查
UPPER(name)和LOWER(email),得分别建两个索引,不能复用
MySQL 8.0+ 的函数索引需配合生成列
MySQL 不支持直接在表达式上建索引,必须先用 GENERATED COLUMN 把函数结果存下来,再对这个列建索引。
例如加速 JSON_EXTRACT(meta, '$.status') 查询:
ALTER TABLE orders ADD status_code VARCHAR(20) GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(meta, '$.status'))) STORED;
CREATE INDEX idx_orders_status_code ON orders (status_code);
-
STORED是必须的——只有持久化生成列才能建索引;VIRTUAL列不行 - 函数必须是 deterministic,且 MySQL 对 JSON 函数的支持有版本限制(如
JSON_EXTRACT在 5.7 可用,但函数索引只在 8.0+ 支持) - 生成列名不能和已有列重名,也不能是保留字,建议加前缀如
g_status_code
Oracle 和 SQL Server 的等价方案
Oracle 称之为「基于函数的索引(Function-Based Index)」,语法最接近 PostgreSQL:
CREATE INDEX idx_emp_name_upper ON employees (UPPER(last_name));
SQL Server 没有原生函数索引,但可通过「计算列 + 索引」模拟:
ALTER TABLE customers ADD name_upper AS UPPER(name) PERSISTED;
CREATE INDEX idx_customers_name_upper ON customers (name_upper);
- SQL Server 的
PERSISTED等效于 MySQL 的STORED,必须加上才能建索引 - Oracle 默认要求查询中表达式与索引定义严格一致,但可通过设置
QUERY_REWRITE_ENABLED=TRUE启用自动重写(不推荐盲目开启) - 所有数据库都要求函数调用不依赖 session 参数(如
TO_CHAR(date_col, 'YYYY-MM-DD')若受NLS_DATE_FORMAT影响,就可能失效)
真正容易被忽略的是表达式的稳定性:哪怕只是多一个空格、换一种 JSON 路径写法('$.a.b' vs '$."a"."b"'),索引就形同虚设。建完务必用 EXPLAIN 验证是否命中,而不是只看语句长得像。










