MySQL 给 JSON 标量建索引前为什么要先 CAST
MySQL 给 JSON 标量建索引前先 CAST,核心不是“语法要求”,而是先把 JSON 提取结果定型成可索引、可比较的 SQL 标量。例如 payload->>'$.customer_code' 会展开为 JSON_UNQUOTE(JSON_EXTRACT(...)),其结果类型是 LONGTEXT;函数索引又不能给这个结果指定前缀长度,所以直接建索引会失败。转换为 CHAR(32) 后,MySQL 才能为隐藏生成列确定长度、排序规则和索引键格式。
- 字符串标量:用
CAST(... AS CHAR(n)),同时确定大小写与排序规则。 - 数值标量:用
UNSIGNED、SIGNED或DECIMAL(p,s),不要按字符串排序。 - 查询条件应复用索引中的路径、类型、长度与排序规则,否则优化器可能无法匹配。
本文以 MySQL 8.4 参考手册为事实边界,官方入口为 https://dev.mysql.com/doc/refman/8.4/en/create-index.html。
先给出一套可用写法
下面直接给 customer_code 建函数索引。双层括号中,外层是索引键列表,内层表示函数表达式:
-- 把 JSON 字符串标量限制为最长 32 个字符,再创建函数索引
CREATE INDEX idx_customer_code
ON orders ((
CAST(
JSON_UNQUOTE(JSON_EXTRACT(payload, '$.customer_code'))
AS CHAR(32)
)
));
-- 查询复用相同路径、类型与长度,避免表达式不匹配
SELECT id, payload
FROM orders
WHERE CAST(
JSON_UNQUOTE(JSON_EXTRACT(payload, '$.customer_code'))
AS CHAR(32)
) = 'C1024';
如果项目允许使用 JSON_VALUE,写法会更紧凑。它的 RETURNING 子句直接声明结果类型;官方文档也将它说明为 CAST(JSON_UNQUOTE(JSON_EXTRACT(...)) AS type) 的简化形式:
-- 直接声明 JSON 路径返回无符号整数,并为该表达式建索引 CREATE INDEX idx_customer_id ON orders ((JSON_VALUE(payload, '$.customer_id' RETURNING UNSIGNED))); -- WHERE 使用与索引完全相同的 JSON_VALUE 表达式 SELECT id, payload FROM orders WHERE JSON_VALUE(payload, '$.customer_id' RETURNING UNSIGNED) = 1024;
为什么 JSON 标量不能直接拿来做函数索引键
MySQL 的 JSON 列本身不能像普通字符串列那样直接创建 B-tree 索引。常见做法是从文档中提取一个标量,再索引生成列或表达式。问题出在“提取”与“索引”之间:JSON 路径只说明从哪里取值,并没有替业务确定这个值应按字符串、整数、金额还是时间比较。
对字符串路径来说,->> 等价于 JSON_UNQUOTE(JSON_EXTRACT(...))。后者返回 LONGTEXT。普通长文本索引往往需要前缀长度,而函数索引的键表达式不能写列前缀,结果就是缺少可用的索引边界。CAST(... AS CHAR(32)) 会让隐藏虚拟生成列得到一个有界的字符串类型,索引键的最大长度也随之确定。

从字段到查询的完整流程
可以把这件事固定成四段:先定路径,再定类型,然后建索引,最后对齐查询。
一、锁定单一 JSON 路径
先确认索引服务的是哪个谓词,例如客户编号、订单金额或发生时间。不要为了“以后可能会用”而给整份 JSON 文档做宽泛设计。标量路径不存在时,JSON_VALUE 默认返回 SQL NULL;如果业务不能接受,应该显式选择 ERROR ON EMPTY 或给出同类型默认值。
二、按比较语义选择类型
类型选择决定索引中的排序和比较。订单金额若用字符串保存,'100' 可能排在 '20' 前面;改成 DECIMAL(12,2) 才是金额语义。纯数字 ID 没有负数时可用 UNSIGNED;业务编码则通常保留字符串。
| JSON 标量用途 | 推荐类型 | 需要额外确认 |
|---|---|---|
| 业务编码、用户名 | CHAR(n) | 最大长度、字符集、大小写规则 |
| 正整数 ID、数量 | UNSIGNED | 是否可能出现负数、非数字文本 |
| 金额、比率 | DECIMAL(p,s) | 总位数、小数位、超界处理 |
| 日期时间文本 | DATE 或 DATETIME | 格式、时区、非法值处理 |

三、在函数索引和生成列之间选择
函数索引更短,适合只为查询加速的单一表达式。它在内部仍以隐藏虚拟生成列实现。显式生成列更适合需要复用列名、查看转换结果或把类型契约写进表结构的场景:
-- 显式生成列便于检查类型结果,也能复用普通列名查询
ALTER TABLE orders
ADD COLUMN customer_code VARCHAR(32)
GENERATED ALWAYS AS (
CAST(
JSON_UNQUOTE(JSON_EXTRACT(payload, '$.customer_code'))
AS CHAR(32)
)
) STORED,
ADD INDEX idx_customer_code (customer_code);
-- 查询生成列时不必重复 JSON 表达式
SELECT id, payload
FROM orders
WHERE customer_code = 'C1024';
若只需要索引而不需要保存生成值,可以改用虚拟生成列。选择 STORED 还是 VIRTUAL 属于写入成本、读取成本与维护方式的权衡,不改变“先定型再索引”的原则。
四、用 EXPLAIN 检查表达式是否命中
建完索引后,不要仅凭索引存在就判断成功。先把真实查询放进 EXPLAIN,查看 possible_keys 与 key。如果没命中,逐项比较 JSON 路径、RETURNING 或 CAST 类型、长度和排序规则:
-- 计划检查只验证优化器选择,不代表实际返回行数 EXPLAIN SELECT id FROM orders WHERE JSON_VALUE(payload, '$.amount' RETURNING DECIMAL(12,2)) >= 100.00;
推荐流程:优先让类型契约显式可见
- 从生产查询中选出一个高频等值或范围谓词,冻结 JSON 路径。
- 根据业务含义选择 SQL 类型,不根据 JSON 文本长什么样猜类型。
- 新写法优先考虑
JSON_VALUE(... RETURNING type);已有JSON_EXTRACT代码则使用显式CAST。 - 字符串索引同时固定长度和排序规则;数值索引固定精度与符号。
- 索引与查询复用同一表达式,再用
EXPLAIN核对。
常见误区
- 只写
payload->>'$.name':它得到LONGTEXT,没有给函数索引提供可用的前缀边界。 - 索引 CAST 了,WHERE 没 CAST:字符串排序规则可能不同。官方示例中,
CAST默认排序规则与JSON_UNQUOTE的二进制排序规则不同,索引可能因此不被使用。 - 数字统一转 CHAR:这样得到的是字典序,不适合范围查询、金额和统计比较。
- 忽略异常值:路径缺失、对象或数组、非法数字和截断都会影响转换结果。先决定返回
NULL、默认值还是报错。 - 把 JSON 数组当标量:数组成员检索属于多值索引场景,不是本文讨论的单标量函数索引。
速查表
| 现象 | 原因 | 处理 |
|---|---|---|
| 函数索引创建失败 | 提取结果是无前缀的 LONGTEXT | 转换为 CHAR(n) 或用 JSON_VALUE RETURNING |
| 索引存在但查询未命中 | 路径、类型、长度或排序规则不同 | 让 WHERE 与索引表达式完全一致 |
| 范围排序不符合数字大小 | 把数字按字符串索引 | 改用 UNSIGNED 或 DECIMAL |
| 缺失路径得到空值 | JSON_VALUE 默认 NULL ON EMPTY | 按业务设置 DEFAULT 或 ERROR ON EMPTY |
相关问题
CAST 是为了把 JSON 字符串截短吗
不只是截短。它同时声明 SQL 类型、最大长度和比较语义。长度应覆盖业务允许的最大值,过小会触发截断问题,过大则增加索引键空间。
JSON_VALUE 不写 RETURNING 可以吗
可以,但默认返回 VARCHAR(512)。对金额、整数、日期等字段,显式写 RETURNING 能避免字符串比较语义,也让表结构和查询意图更清楚。
字符串索引为什么还要关心 COLLATE
因为排序规则决定大小写、重音和二进制比较方式。索引表达式与 WHERE 的排序规则不一致时,即使文本看起来相同,也可能无法匹配同一个函数索引。
什么时候用生成列而不是函数索引
需要复用列名、查看转换结果、加入更多列约束,或希望类型契约在表结构中清晰可见时,用显式生成列更直观;只想为单一表达式加速时,函数索引更简洁。
artworkout怎么创建账号?邮箱、Apple ID和Google登录方式说明
- 上一篇
- artworkout怎么创建账号?邮箱、Apple ID和Google登录方式说明
- 下一篇
- Go 接口里装了 nil 指针为什么判断不等于 nil
-
- 数据库 · MySQL | 3小时前 | MySQL · 事务 · InnoDB · 锁定读 MySQL NOWAIT FOR UPDATE NOWAIT FOR SHARE NOWAIT InnoDB行锁 ERROR 3572
- MySQL NOWAIT 怎么让锁定读立即失败
- 253浏览 收藏
-
- 数据库 · MySQL | 8小时前 | MySQL · JSON · mysql JSON_TABLE ON EMPTY ON ERROR
- MySQL JSON_TABLE 的 ON EMPTY 和 ON ERROR 怎么分别处理
- 251浏览 收藏
-
- 数据库 · MySQL | 12小时前 | MySQL · 执行计划 · mysql ANALYZE TABLE COLUMN_STATISTICS 直方图统计信息
- MySQL 直方图统计信息什么时候需要手动更新
- 306浏览 收藏
-
- 数据库 · MySQL | 17小时前 |
- MySQL 不可见索引怎么验证删除索引的风险
- 358浏览 收藏
-
- 数据库 · MySQL | 18小时前 | MySQL · metadata_locks DDL阻塞 MySQL metadata lock 阻塞会话
- MySQL DDL 卡在 metadata lock 怎么找阻塞会话
- 228浏览 收藏
-
- 数据库 · MySQL | 21小时前 |
- MySQL Buffer Pool 怎么在重启后预热常用页
- 425浏览 收藏
-
- 数据库 · MySQL | 23小时前 | MySQL · 性能排查 · mysql prepared statement reprepare Com_stmt_reprepare
- MySQL Prepared statement 为什么会自动重新预编译
- 387浏览 收藏
-
- 数据库 · MySQL | 1天前 | MySQL · 字符集 · mysql collation Illegal mix of collations COERCIBILITY
- MySQL 字符串比较报 Illegal mix of collations 怎么定位
- 486浏览 收藏
-
- 数据库 · MySQL | 1天前 | MySQL · mysql 备份恢复 mysqldump GTID_PURGED
- MySQL 导入备份时 GTID_PURGED 冲突怎么处理
- 455浏览 收藏
-
- 数据库 · MySQL | 1天前 | MySQL ·
- MySQL 多条复制过滤规则按什么顺序生效
- 382浏览 收藏
-
- 数据库 · MySQL | 1天前 | MySQL · InnoDB · 数据库运维 · mysql 死锁 错误日志 events_statements_history_long data_lock_waits Performance Schema
- MySQL 怎么从 Performance Schema 汇总近期死锁
- 372浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 343次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 403次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 404次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 363次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 185次使用
-
- 接口返回 200 但前端仍报错怎么办:从响应格式到跨域一步步排查
- 2026-06-14 332浏览
-
- MySQL 明明加了索引,为什么查询还是很慢?先查这 6 个点
- 2026-06-27 374浏览
-
- golang MySQL实现对数据库表存储获取操作示例
- 2022-12-22 499浏览
-
- golang 基于 mysql 简单实现分布式读写锁
- 2023-01-07 384浏览
-
- 详解如何利用GORM实现MySQL事务
- 2023-01-07 184浏览

