MySQL 8.0 多值索引必须通过 CAST(... AS ... ARRAY) 将 JSON 数组元素转为统一 SQL 类型数组,隐式生成虚拟列并在此上创建函数索引;仅支持 MEMBER OF、JSON_CONTAINS、JSON_OVERLAPS 等特定查询触发,复合索引中仅允许一个多值键部分。

MySQL 8.0 多值索引必须用 CAST(… AS … ARRAY)
多值索引不是直接在 JSON 数组上建的,而是靠 CAST 把数组元素转成 SQL 类型数组,再基于这个数组隐式生成虚拟列,最后在虚拟列上建函数索引。没这一步,CREATE INDEX 会报错或建出无效索引。
常见错误现象:ERROR 3152 (HY000): Cannot create a functional index on a non-stored generated column,本质是没显式声明数组类型。
-
CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)是合法写法;custinfo->'$.zipcode'单独用会失败 - 数组元素类型必须统一且可映射到 SQL 类型(如
UNSIGNED、CHAR(10)),不能混用字符串和数字 - JSON 路径必须指向数组(如
$.tags),指向对象或标量(如$.name)会报Invalid cast
哪些查询能真正命中多值索引
优化器只在明确使用特定 JSON 函数时才启用多值索引,不是所有带 -> 或 ->> 的查询都能走。
- 有效触发:
WHERE 94582 MEMBER OF(custinfo->'$.zipcode')、WHERE JSON_CONTAINS(custinfo, '"94582"', '$.zipcode')、WHERE JSON_OVERLAPS(custinfo->'$.zipcode', CAST('[94582]' AS JSON)) - 完全不触发:
WHERE custinfo->>'$.zipcode' LIKE '%94582%'(字符串匹配,走不了多值索引) -
JSON_CONTAINS()第二个参数必须是字面量或变量,不能是列名;路径参数必须精确匹配建索引时的路径
复合索引里只能有一个多值键部分
想把多值字段和其他普通列一起建联合索引,语法上允许,但有硬性限制:多值部分只能出现一次,且位置任意。
比如这个语句合法:CREATE INDEX idx_comp ON customers (id, CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY), modified);但下面这个会报错:CREATE INDEX idx_bad ON customers (CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY), CAST(custinfo->'$.tags' AS CHAR(20) ARRAY))。
- 多值部分在复合索引中仍遵循最左前缀原则 —— 查询若不含
id,只查 zipcode 数组,依然能用上该索引 - 性能影响:多值部分会让索引体积显著增大,因为每个 JSON 数组元素都会生成一条索引记录(如
[94582,94536]→ 两条索引项) - 别指望它加速
ORDER BY或GROUP BY多值字段 —— MySQL 不支持对多值索引做排序
EXPLAIN 看不到多值索引名,但能确认是否生效
执行 EXPLAIN 时,key 列显示的是底层虚拟列的名称(如 func_1),不是你起的索引名;但只要 type 是 range 或更好,且 Extra 出现 Using index condition,就说明多值索引参与了过滤。
- 如果
key为NULL,先检查查询是否用了支持的函数(MEMBER OF等),再确认CAST类型是否和查询值一致(比如索引用UNSIGNED,但查询传了负数) -
rows值偏高不等于没走索引 —— 多值索引的行数预估常偏保守,实际性能提升需结合EXPLAIN ANALYZE看真实actual rows - 最容易被忽略的一点:多值索引不支持
SELECT … FOR UPDATE场景下的锁升级,可能引发意料外的锁等待











