MySQL JSON_EXTRACT 查询为什么慢:用生成列索引做一次可验证优化实验
来源:17golang原创
时间:2026-07-22 12:11:10 278浏览 收藏
订单表里的渠道、地区和设备类型经常被塞进 JSON,业务查询写成 JSON_EXTRACT(order_attrs, '$.region') = '华东' 很自然,但普通索引往往帮不上忙。下面用一张小型 orders 表复现这个问题,再把查询字段落成生成列,最后用 EXPLAIN 核对优化器是否真的换了执行路径。
JSON_EXTRACT 走得慢的核心原因是普通索引覆盖不到 JSON 内部字段的计算逻辑,全量读取行数据后再做 JSON 解析会吃掉大量 IO,用和查询语义完全对齐的生成列建索引,就能把这部分筛选下压到索引层,大幅减少需要回表扫描的数据量。
- JSON 文档里被频繁筛选的字段,不能只看“字段有索引”就判断查询会变快。
- 生成列要保持类型、路径和业务条件一致,
CAST与空值处理会影响索引命中。 - 优化前后至少对照
key、rows、Extra,再结合实际耗时判断是否值得改表。 - 联合索引的列顺序应贴合最常用的过滤条件,不能把所有 JSON 字段都提前展开。
先准备一条能复现的 JSON 查询
实验使用 MySQL 8.0 语法,假设订单表已经存在 order_attrs JSON 列。示例数据只为了观察查询计划,实际项目中可以替换成线上脱敏结构。
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT NOT NULL,
amount DECIMAL(10, 2) NOT NULL,
order_attrs JSON NOT NULL,
created_at DATETIME NOT NULL,
KEY idx_created_at (created_at)
);
INSERT INTO orders (user_id, amount, order_attrs, created_at) VALUES
(1001, 199.00, '{"region":"华东","channel":"app","device":"ios"}', '2026-07-20 10:00:00'),
(1002, 88.50, '{"region":"华南","channel":"web","device":"android"}', '2026-07-20 10:05:00');
把数据量补到真实量级后,再观察下面这条语句。这里不要急着加索引,先保存优化前的执行计划。
EXPLAIN
SELECT id, user_id, amount
FROM orders
WHERE JSON_UNQUOTE(JSON_EXTRACT(order_attrs, '$.region')) = '华东'
AND created_at >= '2026-07-01';
为什么普通索引没有直接解决 JSON_EXTRACT
普通索引建在 created_at 上,只能帮助时间范围过滤;JSON 条件是对列内容再做函数计算。计划中的 key 可能仍然是 idx_created_at,也可能为空,关键要看 rows 是否明显偏大,以及 Extra 是否出现额外筛选。

如果 created_at 的范围已经很窄,普通索引可能仍然是合理选择;如果时间范围覆盖了大量订单,数据库就要读出更多行后逐条解析 JSON。优化方向不是“JSON 一律不能查”,而是把稳定、常用的筛选路径显式暴露出来。
把 region 落成生成列并建立联合索引
先增加一个与原条件语义一致的生成列。JSON_UNQUOTE 用来去掉字符串值的 JSON 引号,字段类型则按业务值长度给出边界。
ALTER TABLE orders
ADD COLUMN region_name VARCHAR(32)
GENERATED ALWAYS AS (
JSON_UNQUOTE(JSON_EXTRACT(order_attrs, '$.region'))
) STORED;
ALTER TABLE orders
ADD INDEX idx_region_created (region_name, created_at);
这里选 STORED 是为了让生成结果随行保存,写入时多一点计算,读取时可以直接走索引。若查询只按地区筛选,单列索引也够用;本例还带时间范围,所以把 created_at 放在第二列。

用同一组条件核对优化是否生效
查询条件改为生成列后,先看计划再看实际运行。不要只凭 SQL 看起来更短就下结论。
EXPLAIN
SELECT id, user_id, amount
FROM orders
WHERE region_name = '华东'
AND created_at >= '2026-07-01';
重点核对这三项:
key是否变成idx_region_created。rows估算值是否从大范围扫描降到更接近目标数据量。Extra是否少了不必要的额外筛选;如果仍然读了很多行,要回头看数据分布和索引顺序。
在支持的环境中,可以用 EXPLAIN ANALYZE 对照实际行数与耗时。测试时固定时间范围,至少跑几次,避开刚好命中缓存的单次结果。
空值、类型和联合索引顺序是三个边界
生成列的表达式必须和查询语义稳定一致。JSON 中没有 region 时结果通常是 NULL,不要把它误当成空字符串;如果业务允许数字地区码,应该显式转换类型,避免字符串比较和数字比较混在一起。
| 检查项 | 容易出现的偏差 | 处理建议 |
|---|---|---|
| 路径 | $.region 与 $.area 混用 | 统一字段契约,先查样本数据 |
| 类型 | 字符串值与数字值比较 | 在生成列表达式中明确类型 |
| 顺序 | 把低频字段放在联合索引第一列 | 按常用过滤与选择性复核 |
| 写入代价 | 一次展开过多 JSON 字段 | 只为稳定高频条件建生成列 |
常见问题
生成列一定比直接查询 JSON 更快吗?
不一定。时间范围很小、数据量不大或 JSON 条件选择性很低时,改表的收益可能有限,应该用计划和实测结果决定。
可以直接给 JSON 列建立普通索引吗?
要看字段类型和查询方式。MySQL 支持针对 JSON 路径的函数索引写法,但团队需要统一表达式;生成列更容易被检查、复用和解释。
为什么加了索引,计划还是没有选它?
常见原因是统计信息、数据分布、条件选择性或表达式不一致。先确认查询确实使用了生成列,再更新统计信息并重新比较。
在线表很大时能直接 ALTER TABLE 吗?
不要把实验命令直接搬到生产。先评估表大小、写入峰值、锁等待和变更工具,安排灰度窗口并准备回退方案。
把实验结果留成一条可复查记录
这次优化的最小闭环是:保存原始 EXPLAIN,增加一列生成列,建立贴合条件的联合索引,再用完全相同的筛选范围复查 key、rows 与实际耗时。只有三个结果都朝预期变化,才值得把它推广到更多 JSON 字段。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
数据库 · MySQL | 1天前 | MySQL · 权限管理 · 备份 · mysqldump · 数据库安全 · 最小权限 mysqldump备份账号 MySQL角色 partial_revokes 备份权限413 收藏
-
数据库 · MySQL | 2天前 | MySQL · JSON · 索引 · 数据库 · 查询优化 · 生成列 · json_extract 索引优化 列表筛选 生成列 MySQL JSON JSON索引351 收藏
-
数据库 · MySQL | 3天前 | MySQL · 认证 · MySQL 8.4 · 数据库升级 · caching_sha2_password mysql_native_password 账号认证 MySQL 8.4 升级迁移236 收藏
-
471 收藏
-
数据库 · MySQL | 5天前 | MySQL · 数据库 · SQL · ON DUPLICATE KEY UPDATE · VALUES · 行别名 · MySQL VALUES() 弃用 ON DUPLICATE KEY UPDATE MySQL 行别名 INSERT AS new MySQL upsert INSERT SELECT117 收藏
-
数据库 · MySQL | 6天前 | MySQL · 索引 · limit · explain · sql优化 · ORDER BY · mysql order by explain limit 复合索引 filesort279 收藏
-
数据库 · MySQL | 1星期前 | 并发 · MySQL · InnoDB · update · 库存扣减 · innodb MySQL 库存扣减 条件 UPDATE 防超卖 affected rows470 收藏
-
421 收藏
-
189 收藏
-
412 收藏
-
378 收藏
-
334 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习