MySQL函数索引需用STORED生成列实现,VIRTUAL列不可索引;JSON字段过滤应提取为STORED列并建索引,注意双箭头语法、长度预估及表达式确定性限制。

MySQL 本身不支持函数索引(如 WHERE UPPER(name) = 'JOHN' 无法直接走 name 索引),但通过 GENERATED COLUMN + 索引,可以等效实现。关键不是“能不能”,而是“怎么选类型、怎么写表达式、怎么验证是否生效”。
什么时候必须用 STORED 而非 VIRTUAL
只有 STORED 生成列才能被索引;VIRTUAL 列虽可定义,但 CREATE INDEX 会报错 ERROR 3107 (HY000): Generated column 'col_name' cannot be indexed。这是硬性限制,不是性能建议。
常见误判场景:
- 以为
VIRTUAL配合INVISIBLE或优化器提示就能触发索引 —— 不行,底层不支持 - 在 JSON 字段上建
VIRTUAL列如json_name VARCHAR(64) AS (col01->>"$.name"),再想加索引 —— 必须改成STORED - 表达式含隐式类型转换(如
CAST(col AS CHAR))却忽略STORED的存储开销,导致写入变慢
JSON 字段过滤加速:用 STORED + 函数提取 + 索引
对 JSON 字段做 WHERE col01->>"$.status" = 'active' 这类查询,默认无法走索引。解决方案是把提取逻辑下沉为 STORED 生成列并索引:
ALTER TABLE orders ADD COLUMN status_str VARCHAR(20) AS (col01->>"$.status") STORED, ADD INDEX idx_status_str (status_str);
注意三点:
-
->>"$.."必须用双箭头(去引号),否则生成列值带双引号,导致等值匹配失败 - 目标字段长度要预估充分,
VARCHAR(20)太小会截断,太大又浪费空间 - 该列会随
col01更新自动刷新,但插入时若col01缺失$.status路径,则status_str为NULL,需在业务或约束层兜底
如何确认优化真正生效
不能只看 EXPLAIN 显示 key 字段有值,还要检查 Extra 是否含 Using index condition 或 Using where; Using index。更关键的是比对重写前后的 EXPLAIN FORMAT=TREE 输出:
原始查询:SELECT * FROM orders WHERE col01->>"$.status" = 'active';
执行 EXPLAIN FORMAT=TREE 后,若看到类似:
> Index lookup on orders using idx_status_str (status_str = 'active')
说明优化器已将表达式匹配到生成列索引;如果仍是 Full table scan 或 Using where,大概率是生成列表达式与查询谓词不完全等价(比如用了 -> 而非 ->>,或大小写不一致)。
容易被忽略的表达式限制
生成列表达式不能包含任何非确定性函数,否则建表或加列会失败。典型报错:ERROR 3105 (HY000): The value specified for generated column 'xxx' is not allowed。
明确禁止的包括:
-
NOW(),CURRENT_TIMESTAMP,SYSDATE() -
RAND(),UUID() -
USER(),CURRENT_USER() - 子查询、外部表引用、自定义函数
哪怕只是表达式里嵌了一层 IF(col IS NULL, '', UPPER(col)),只要 UPPER() 输入是确定的,就合法;但换成 IF(RAND() > 0.5, 'a', 'b') 就立刻失败。


















