MySQL 5.7 开始原生支持 JSON 数据类型,8.0 进一步增强了JSON函数、JSON表、生成列索引能力。很多业务场景(扩展属性、动态配置、标签)会选择JSON字段存储,但是很多人只会简单插入,不清楚如何提取、修改、索引JSON内部数据,本文结合实战SQL讲解JSON的增删改查与避坑要点。
一、建表:JSON字段基础定义
创建测试表,使用json类型,不要用varchar代替原生JSON类型。原生JSON会做语法校验,非法JSON无法写入。
sql
CREATE TABLE `product` (
`id` INT PRIMARY KEY AUTO_INCREMENT,
`name` VARCHAR(100) NOT NULL,
`ext_info` JSON COMMENT '扩展信息,JSON类型'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
二、插入JSON数据
可以直接写JSON字符串插入,MySQL会自动校验JSON合法性。
sql
-- 插入单行JSON
INSERT INTO product(name,ext_info)
VALUES('手机', '{"color":"黑色","price":3999,"tags":["数码","5G"],"attr":{"weight":"200g"}}');
-- 多行插入
INSERT INTO product(name,ext_info)
VALUES
('电脑','{"color":"灰色","price":5999,"tags":["数码","笔记本"],"attr":{"weight":"1.2kg"}}'),
('耳机','{"color":"白色","price":299,"tags":["数码","音频"],"attr":{"weight":"8g"}}');
三、查询JSON数据(核心)
1. -> 和 ->> 提取JSON字段
->:返回JSON类型,保留引号->>:返回字符串,日常业务查询优先用 ->>,会去掉外层引号
sql
-- 提取ext_info中的color
SELECT
id,
name,
ext_info -> '$.color' AS color_json,
ext_info ->> '$.color' AS color_str
FROM product;
$代表整个JSON对象,$.key 代表取key字段。
2. 查询JSON数组
JSON数组通过下标取值,下标从0开始:
sql
-- 获取tags数组第0个元素
SELECT id, name, ext_info ->> '$.tags[0]' as tag1 FROM product;
3. JSON_CONTAINS 判断是否包含元素
常用于标签数组匹配,查找tags包含数码的商品
sql
SELECT id,name,ext_info
FROM product
WHERE JSON_CONTAINS(ext_info->'$.tags', '"数码"');
4. JSON_EXTRACT 函数写法(等价 ->)
sql
SELECT JSON_EXTRACT(ext_info, '$.price') FROM product;
四、更新JSON数据
使用JSON_SET、JSON_INSERT、JSON_REPLACE、JSON_ARRAY_APPEND
JSON_SET:存在则更新,不存在则新增(最常用)
sql
-- 修改price,新增stock字段
UPDATE product
SET ext_info = JSON_SET(ext_info, '$.price', 3799, '$.stock', 100)
WHERE id=1;
JSON_INSERT:只新增,已有key不修改JSON_REPLACE:只修改,不存在key不新增JSON_ARRAY_APPEND:给数组追加元素
sql
-- tags数组新增"快充"标签
UPDATE product
SET ext_info = JSON_ARRAY_APPEND(ext_info, '$.tags', "快充")
WHERE id=1;
JSON_REMOVE删除JSON内指定key
sql
-- 删除stock字段
UPDATE product
SET ext_info = JSON_REMOVE(ext_info, '$.stock')
WHERE id=1;
五、JSON生成列 + 索引(重点优化)
原生JSON字段不能直接建索引 ,如果需要根据JSON内部字段快速检索,必须创建生成列,再给生成列加索引。
sql
-- 新增生成列,提取price,然后创建索引
ALTER TABLE product ADD COLUMN ext_price INT GENERATED ALWAYS AS (ext_info ->> '$.price') STORED;
CREATE INDEX idx_product_ext_price ON product(ext_price);
之后查询可以直接使用生成列,走索引:
sql
SELECT * FROM product WHERE ext_price>3000;
注意:
STORED表示实际存储数据,VIRTUAL不存储(默认),InnoDB只能对STORED生成列建立索引。
六、JSON_TABLE:JSON数组转多行(MySQL8.0特有)
如果JSON里是数组,想要一行拆成多条记录,使用JSON_TABLE。
sql
SELECT p.id, p.name, jt.tag
FROM product p,
JSON_TABLE(
p.ext_info -> '$.tags',
'$[*]' COLUMNS (tag VARCHAR(32) PATH '$')
) jt;
这条SQL会把tags数组的每一个元素单独一行输出,做统计、关联查询非常好用。
七、常用JSON函数汇总
| 函数 | 作用 |
|---|---|
| JSON_EXTRACT | 提取JSON值 |
| JSON_SET | 更新/新增json键 |
| JSON_REMOVE | 删除json键 |
| JSON_ARRAY_APPEND | 数组追加元素 |
| JSON_CONTAINS | 判断包含 |
| JSON_VALID | 判断字符串是否合法JSON |
| JSON_LENGTH | 获取JSON数组/对象长度 |
八、坑点总结
- 不要大量在
WHERE条件直接写ext_info->>'$.xxx',没有索引,全表扫描,高频查询字段一定要用生成列索引。 - JSON查询大小写敏感,匹配字符串时注意。
- MySQL5.7没有
JSON_TABLE,数组拆行需要在应用层处理。 - JSON字段不支持传统的联合索引,只能针对生成单列建索引。
- 复杂JSON嵌套层级很深时,查询性能会下降,核心字段尽量提为独立普通列。
九、选型建议
- 适合:动态扩展属性、不确定字段、少量查询筛选,数据结构经常变更。
- 不适合:需要频繁WHERE、JOIN、排序、统计的核心字段,优先单独建列。