必须用 CAST(... AS ... ARRAY) 显式声明数组类型,否则多值索引无法创建或不生效;MySQL 要求 JSON 路径指向数组、元素类型统一,且仅特定查询语法(如 MEMBER OF、JSON_CONTAINS 带路径)能命中索引。

必须用 CAST(... AS ... ARRAY) 显式声明数组类型,否则索引建不成或不生效。
多值索引创建时必须显式 CAST 数组元素类型
MySQL 不允许直接在 custinfo->'$.tags' 这类 JSON 路径表达式上建多值索引。错误现象是 ERROR 3152 (HY000): Cannot create a functional index on a non-stored generated column —— 本质是没告诉 MySQL “这个路径下每个元素该当什么 SQL 类型”。
-
CAST(custinfo->'$.tags' AS CHAR(20) ARRAY)合法,假设 tags 全是短字符串 -
CAST(custinfo->'$.zipcode' AS UNSIGNED ARRAY)合法,用于纯数字邮政编码 -
custinfo->'$.tags'单独写进CREATE INDEX会失败 - 路径必须指向数组(如
$.tags),指向对象($.profile)或标量($.name)会报Invalid cast - 数组内元素类型必须统一;混用
"94582"和94582会导致 CAST 失败
只有特定查询能命中多值索引
不是所有带 JSON_CONTAINS 的 WHERE 条件都能走索引。优化器只识别明确的、路径匹配的调用模式。
- 有效触发:
WHERE 'tech' MEMBER OF(custinfo->'$.tags') - 有效触发:
WHERE JSON_CONTAINS(custinfo, '"tech"', '$.tags')(第二个参数必须是字面量或变量,不能是列名) - 有效触发:
WHERE JSON_OVERLAPS(custinfo->'$.tags', CAST('["tech"]' AS JSON)) - 完全不触发:
WHERE custinfo->>'$.tags' LIKE '%tech%'(字符串扫描,无法利用多值索引) - 不触发:
WHERE JSON_CONTAINS(custinfo, 'tech')(缺路径参数,查整个文档)
复合索引中多值部分只能出现一次
想把标签查询和时间范围、状态字段一起加速?可以建复合索引,但有硬性限制。
- 语法允许:
CREATE INDEX idx_status_tags ON products (status, (CAST(attributes->'$.categories' AS CHAR(32) ARRAY)), created_at) - 但多值键部分最多一个,且不能重复出现
- 位置任意:多值部分可放最前、中间或最后,不影响语法合法性
- 实际效果取决于查询条件覆盖度;如果只查
status和created_at,多值部分不会参与索引查找 - 不要试图用两个 JSON 数组路径建联合多值索引,MySQL 会拒绝
建索引前务必确认数据一致性
多值索引依赖虚拟列隐式生成,而虚拟列的值由 CAST 表达式实时计算。一旦源 JSON 数据不符合预期结构,索引条目可能为空或错乱。
- 先跑一遍
SELECT COUNT(*) FROM t WHERE JSON_VALID(custinfo)=0,排除非法 JSON - 检查路径是否真指向数组:
SELECT JSON_TYPE(custinfo->'$.tags') FROM t LIMIT 5,结果应全为ARRAY - 抽样验证 CAST 是否成功:
SELECT CAST(custinfo->'$.tags' AS CHAR(20) ARRAY) FROM t WHERE custinfo->'$.tags' IS NOT NULL LIMIT 5,避免 NULL 或空数组导致隐式列值异常 - 如果业务允许,建议在应用层写入时就约束 JSON 结构,比事后补救更可靠
最容易被忽略的是 CAST 类型与实际数据的对齐——比如用 UNSIGNED 去 CAST 可能含负数或空字符串的字段,会导致整行 CAST 失败,该行就无法进入多值索引。宁可宽泛选 CHAR(N),也别在类型上赌运气。


















