字数:约4000字 | 阅读时间:12分钟
“JSON不是魔鬼,但把它用错地方的人正在制造一场灾难。”


一、问题背景:MySQL 8.4 来了

2026年4月,MySQL 8.4 正式发布,这是8.0 LTS系列的第三个维护版本,也是目前最新的稳定版。作为MySQL 8.0 LTS(Long Term Support)路线图的最后一棒,8.4承担着为8.0系列画上句号的重任——下一个LTS将是MySQL 9.0。

为什么8.4值得关注?

  • JSON功能大幅增强,特别是JSON_TABLE和路径表达式的索引支持
  • 查询优化器对JSON表达式有更深入的理解
  • 性能优化覆盖连接池、索引合并、字符串函数等多个层面
  • 兼容性与迁移工具更完善

很多团队还在用MySQL 5.7,甚至8.0的早期版本。今天这篇文章,是时候让你认真看看8.4了。


二、MySQL 8.4 版本定位

MySQL采用了两条腿走路的版本策略:

版本线 特点 适用场景
Innovation(创新版) 每季度发布,功能最新,8.4/8.5/8.6 尝鲜、测试环境
LTS(长期支持版) 两年维护周期,8.0/9.0 生产环境首选

MySQL 8.4 是 Innovation 版本,而非 LTS。 如果你需要LTS稳定性,应该用MySQL 8.0(将维护至2027年4月),或者等待2027年的MySQL 9.0 LTS。

但即便8.4不是LTS,它的JSON增强和性能改进对生产环境依然有直接价值——前提是你不把它部署在最保守的金融核心系统里。

1
2
3
# 查看当前MySQL版本
mysql --version
# mysql Ver 8.4.0 for Linux on x86_64 (MySQL Community Server - GPL)

三、JSON增强:从”能存”到”能用”

MySQL从5.7开始支持JSON类型,但早期的JSON操作性能一直被吐槽。8.4的改进让JSON从”勉强能用”变成”可以上生产”。

3.1 JSON路径表达式索引(新增)

这是8.4最大的JSON相关改进——终于可以为JSON路径表达式创建索引了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 创建表,age字段是JSON
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
profile JSON NOT NULL,
-- 创建Generated Column索引
age INT GENERATED ALWAYS AS (JSON_EXTRACT(profile, '$.age')) STORED,
INDEX idx_age (age)
);

-- 插入数据
INSERT INTO users (profile) VALUES
('{"name": "张三", "age": 28, "city": "北京"}'),
('{"name": "李四", "age": 35, "city": "上海"}'),
('{"name": "王五", "age": 42, "city": "深圳"}');

-- 现在这个查询可以走索引了
EXPLAIN SELECT * FROM users WHERE age > 30;
-- 看到 idx_age 被使用

原理: 通过Generated Column将JSON路径表达式物化为普通列,然后为它建索引。8.4之前这招也能用,但8.4优化器对这类表达式的理解更深,不会轻易放弃索引。

对比PostgreSQL: PostgreSQL早就有jsonb_path_ops索引,MySQL的方案更接近Gin索引的思想,但实现不同。

3.2 JSON_TABLE:JSON转表格的利器

JSON_TABLE函数把JSON数组转成虚拟表,让SQL的力量直接作用于JSON结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- 假设有个订单表,details字段存储商品列表
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
customer_id BIGINT,
details JSON
);

INSERT INTO orders VALUES
(1, 1001, '[{"product": "手机", "qty": 2, "price": 5999}, {"product": "耳机", "qty": 1, "price": 299}]'),
(2, 1002, '[{"product": "笔记本", "qty": 1, "price": 8999}]');

-- 用JSON_TABLE展开商品明细
SELECT
o.customer_id,
jt.product,
jt.qty,
jt.price,
jt.qty * jt.price AS subtotal
FROM orders o,
JSON_TABLE(
details,
'$[*]' COLUMNS (
product VARCHAR(50) PATH '$.product',
qty INT PATH '$.qty',
price DECIMAL(10,2) PATH '$.price'
)
) AS jt;

输出:

customer_id product qty price subtotal
1001 手机 2 5999.00 11998.00
1001 耳机 1 299.00 299.00
1002 笔记本 1 8999.00 8999.00

这在实际业务中有什么用?

  • 订单商品明细的聚合查询
  • 报表统计(按商品维度汇总)
  • 和普通表做JOIN

3.3 JSON搜索增强

8.4对JSON_CONTAINSJSON_SEARCH的索引利用有改进,特别是在组合条件下:

1
2
3
4
5
6
-- 创建JSON全文索引(8.0已有,8.4优化器改进)
ALTER TABLE users ADD FULLTEXT INDEX idx_profile_ft(profile);

-- 性能更好的JSON搜索方式
SELECT * FROM users
WHERE JSON_EXTRACT(profile, '$.tags') = 'vip';

四、性能优化:不止是JSON

4.1 查询优化器改进

MySQL 8.4在查询优化器上有几个关键改进:

1. 范围优化器增强
对多列索引的范围查询有更智能的判断,减少不必要的全索引扫描。

2. 索引合并策略优化
之前版本在同时使用多个索引时可能选择次优方案,8.4的cost模型更精确。

3. 派生条件化简
优化器能识别子查询中的冗余条件,直接在优化阶段剔除,而不是留到执行阶段。

1
2
3
4
-- 8.4优化器能自动识别这个条件的冗余性
SELECT * FROM orders
WHERE status = 'completed'
AND status != 'cancelled'; -- 前一个条件已经隐含了非cancelled

4.2 JSON函数性能提升

几个常用JSON函数的执行效率在8.4中有改善:

函数 8.0耗时 8.4耗时(估算) 提升
JSON_EXTRACT 100% 85% ~15%
JSON_SET 100% 78% ~22%
JSON_ARRAYAGG 100% 72% ~28%

具体提升取决于数据结构和查询复杂度,但在JSON列上做聚合操作(JSON_ARRAYAGG/JSON_OBJECTAGG)的场景收益明显。

4.3 连接池参数调整

8.4调整了一些默认连接参数,让新手更容易获得合理的开箱即用性能:

1
2
3
4
5
6
7
8
# my.cnf 关键调整
[mysqld]
# 默认连接池大小从151调整到更保守的数值
default_authentication_plugin=caching_sha2_password

# 8.4新增:允许更灵活地配置最大连接数与pool大小的关系
max_connections=200
innodb_thread_concurrency=0 # 让InnoDB自适应

如果你的机器内存够大,可以考虑这个配置:

1
2
3
4
5
[mysqld]
# 32GB内存的机器
innodb_buffer_pool_size = 20G
max_connections = 500
innodb_log_file_size = 2G

4.4 字符集与排序规则优化

8.4对utf8mb4_0900_ai_ci排序器的性能有改善,这是默认推荐的Unicode排序规则。如果你的应用大量使用中文字符排序,这个改进会直接影响ORDER BY的响应时间。

1
2
-- 8.4对中文字符排序性能有优化
SELECT * FROM users ORDER BY name COLLATE utf8mb4_zh_0900_as_cs;

五、实战:JSON数据处理最佳实践

5.1 场景:用户标签系统

很多业务需要给用户打标签,传统做法是单独建关联表,但在快速迭代场景下,用JSON存储更灵活:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
CREATE TABLE customers (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
tags JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- 用于高效查询的Generated Column
vip_level INT GENERATED ALWAYS AS (
CASE
WHEN JSON_CONTAINS(tags, '"svip"') THEN 3
WHEN JSON_CONTAINS(tags, '"vip"') THEN 2
WHEN JSON_CONTAINS(tags, '"new"') THEN 1
ELSE 0
END
) STORED,
INDEX idx_vip_level (vip_level)
);

-- 快速查询高价值用户
SELECT id, name, tags
FROM customers
WHERE vip_level >= 2;

5.2 动态属性配置

电商平台的SKU属性差异巨大,用JSON存储配置比预置字段更灵活:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
CREATE TABLE products (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(200) NOT NULL,
category VARCHAR(50),
attributes JSON, -- 动态属性
price DECIMAL(10,2)
);

INSERT INTO products VALUES
(1, 'iPhone 16', '手机', '{"color": ["黑", "白", "蓝"], "storage": "256G", "防水": true}', 6999.00),
(2, '扫地机器人', '家电', '{"清扫方式": "激光导航", "续航": "180分钟", "噪音": "55dB"}', 2599.00);

-- 查询有"防水"属性且为true的产品
SELECT name, JSON_EXTRACT(attributes, '$.防水') AS waterproof
FROM products
WHERE JSON_EXTRACT(attributes, '$.防水') = TRUE;

-- JSON_TABLE做属性透视
SELECT p.name, jt.attr_name, jt.attr_value
FROM products p,
JSON_TABLE(
attributes,
'$' COLUMNS (
attr_name VARCHAR(50) PATH '$.color',
attr_value VARCHAR(50) PATH '$.storage'
)
) AS jt;

5.3 JSON与JOIN的权衡

什么时候用JSON字段?

  • 属性不固定或频繁变化
  • 不需要基于这些属性做精确的关联查询
  • 数据量级可控(单行JSON不超过几KB)

什么时候用关联表?

  • 需要精确JOIN
  • 属性值需要单独建索引且查询频繁
  • 数据量级很大(百万级以上)

一个简单判断:如果你发现自己经常JSON_EXTRACT(...)=某具体值做WHERE条件,就该考虑用Generated Column建索引了。


六、与PostgreSQL的JSON能力对比

能力 MySQL 8.4 PostgreSQL 17
JSON类型 JSON(动态类型检查) JSON/JSONB(后者二进制,性能更好)
路径索引 Generated Column方案 jsonb_path_ops GIN索引
JSON_TABLE等价 JSON_TABLE(MySQL 8.4) jsonb_array_elements(需要LATERAL)
JSON路径查询 JSON_EXTRACT/JSON_VALUE ->/->> 操作符
JSON聚合 JSON_ARRAYAGG/JSON_OBJECTAGG json_agg/jsonb_agg
向量搜索 无(需额外插件) pgvector扩展(AI场景)

结论: PostgreSQL的JSON能力依然更强,特别是JSONB的类型和pgvector扩展。但MySQL 8.4的JSON功能已经”够用”,且对于已有MySQL技术栈的团队迁移成本更低。


七、迁移建议与性能调优

7.1 从MySQL 8.0升级到8.4

1
2
3
4
5
# 备份(必须!)
mysqldump -u root -p --all-databases > backup_20260512.sql

# 升级检查工具
mysql_upgrade -u root -p

8.0 → 8.4 是原地升级,不需要导出/导入数据,但依然建议在测试环境验证。

7.2 性能调优检查清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- 1. 查看JSON列的存储效率
SELECT
table_name,
engine,
avg_row_length,
data_length
FROM information_schema.tables
WHERE table_schema = 'your_database'
ORDER BY data_length DESC;

-- 2. 检查慢查询(特别是JSON函数相关的)
SELECT * FROM performance_schema.events_statements_summary_by_digest
ORDER BY SUM_TIMER_WAIT DESC
LIMIT 10;

-- 3. JSON索引验证
SHOW CREATE TABLE your_json_table\G
-- 确认有Generated Column且有INDEX

7.3 常见坑

坑1:JSON路径大小写敏感
MySQL的JSON路径表达式对大小写敏感,$.Name$.name是不同的路径。

1
2
3
4
-- 存进去是 camelCase,拿出来也要一致
INSERT INTO t VALUES ('{"userName": "张三"}');
SELECT JSON_EXTRACT(col, '$.userName'); -- 有值
SELECT JSON_EXTRACT(col, '$.username'); -- NULL

坑2:JSON数组的元素位置
$[0]表示第一个元素,不是从1开始。没有JSON_FIRST这种函数,必须用下标。

坑3:JSON的NULL vs SQL的NULL
JSON中的null是合法值,但MySQL的JSON_TYPE返回"null"(字符串),区分于SQL的NULL。

1
2
3
SELECT JSON_TYPE('{"a": null}');  -- "object"
SELECT JSON_EXTRACT('{"a": null}', '$.a'); -- null(SQL NULL)
SELECT JSON_TYPE(JSON_EXTRACT('{"a": null}', '$.a')); -- "null"

八、结论

MySQL 8.4的JSON增强和性能优化,是8.0 LTS系列的收官之作。如果你已经在用MySQL 8.0,升级到8.4是低风险的改进;如果你还在用MySQL 5.7,8.4的JSON能力足够让你考虑迁移。

关键收获:

  1. Generated Column + 索引是JSON查询性能的关键
  2. JSON_TABLE让JSON和SQL的边界变得模糊,好用
  3. 连接池和排序规则的默认优化开箱即用
  4. 迁移前测试,特别是自定义配置较多的环境

记住:JSON是工具,不是设计失败的理由。 用对了,它能让你快速迭代;用错了,它会变成一碗糊成一团的兰州拉面。


相关阅读:


有问题?欢迎留言讨论。