在互联网业务中,我们经常需要存储半结构化数据——比如用户的个性化配置、商品的多维度属性、接口返回的嵌套数据等。传统方案中,要么用VARCHAR/TEXT存储JSON字符串(无法校验格式、查询需全表扫描),要么拆分为多张表(结构僵化、适配性差)。
MySQL 5.7版本正式引入JSON原生数据类型,8.0版本进一步增强了索引和函数支持,完美解决了半结构化数据的存储痛点:既保留了JSON的灵活扩展性,又能利用MySQL原生函数实现高效查询,还支持索引优化。
一、为什么选择MySQL JSON类型?
在使用JSON类型前,先明确它解决了哪些核心问题,对比传统存储方案的优势:
存储方案 | 问题 | MySQL JSON类型优势 |
VARCHAR/TEXT存JSON字符串 | 1. 无格式校验(存错JSON语法也能入库);2. 查询需解析全量字符串,性能差;3. 无法精准修改字段 | 1. 自动校验JSON格式;2. 原生函数支持精准查询/修改;3. 支持索引优化查询 |
拆分多张关联表 | 1. 结构僵化,新增字段需改表;2. 多表关联查询复杂;3. 适配多变的业务属性成本高 | 1. 结构灵活,无需改表即可扩展字段;2. 单表查询,逻辑简单;3. 适配快速迭代的业务 |
适用场景:
- 用户个性化配置(如主题、权限、偏好设置);
- 商品多维度属性(如电商商品的颜色、尺寸、规格,不同品类属性不同);
- 日志/埋点数据(结构不固定的行为日志);
- 第三方接口返回数据(直接存储,无需拆解)。
版本要求:
- MySQL 5.7.8+:支持JSON基础类型和核心函数;
- MySQL 8.0.17+:支持JSON列的直接索引(Multi-Valued Indexes),无需依赖虚拟列;
- 建议使用8.0+版本,功能更完善、性能更优。
二、JSON类型特性
- 格式校验:插入/更新JSON字段时,MySQL会自动校验JSON语法,格式错误会直接报错(如缺少引号、括号不匹配),避免脏数据;
- 原生函数支持:提供数十个JSON专用函数,支持提取、修改、校验、聚合等操作;
- 高效存储:JSON数据以二进制格式存储,比字符串存储更节省空间,且查询时无需重新解析;
- 索引支持:5.7+可通过“虚拟列+索引”优化查询,8.0.17+支持JSON列直接创建索引;
- 兼容性:可与MySQL其他数据类型(如INT、VARCHAR)混合使用,支持事务、主从复制。
三、基础操作:创建、插入、查询JSON数据
先搭建实战环境,以“用户个性化配置”为核心场景,演示JSON类型的基础用法。
3.1 创建含JSON字段的表
创建user_config表,包含id(主键)、user_id(用户ID)、config(JSON类型,存储用户配置):
CREATE TABLE `user_config` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', `user_id` BIGINT NOT NULL COMMENT '用户ID', `config` JSON NOT NULL COMMENT '用户个性化配置(JSON类型)', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', -- 唯一索引:保证一个用户只有一条配置 UNIQUE KEY `idx_user_id` (`user_id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '用户个性化配置表';3.2 插入JSON数据
支持插入对象、数组、嵌套JSON等结构,MySQL会自动校验格式,格式错误会报错。
-- 插入简单JSON对象(用户基础配置)INSERT INTO user_config (user_id, config) VALUES(1001, '{ "theme": "dark", "language": "zh-CN", "notifications": { "push": true, "email": false }, "favorite_modules": ["order", "profile", "message"]}'),-- 插入另一个用户的配置(结构可灵活扩展)(1002, '{ "theme": "light", "language": "en-US", "notifications": { "push": false, "email": true, "sms": true -- 新增字段,无需改表 }, "favorite_modules": ["dashboard", "analytics"]}');-- 错误示例:JSON格式错误(缺少引号),插入失败INSERT INTO user_config (user_id, config) VALUES (1003, '{theme: dark}');3.3 查询JSON数据(核心函数)
MySQL提供多种函数提取JSON数据,最常用的是->和->>(简化版)、JSON_EXTRACT(标准版)。
函数/运算符 | 作用 | 示例(提取user_id=1001的theme) | 结果 |
JSON_EXTRACT() | 提取JSON字段,保留引号 | JSON_EXTRACT(config, '$.theme') | "dark" |
-> | 等价于JSON_EXTRACT,保留引号 | config->'$.theme' | "dark" |
->> | 提取JSON字段,去除引号 | config->>'$.theme' | dark |
基础查询示例
-- 1. 提取单个字段(去除引号,常用)SELECT user_id, config->>'$.theme' AS theme, config->>'$.language' AS languageFROM user_configWHERE user_id = 1001;-- 2. 提取嵌套JSON字段(如notifications下的push)SELECT user_id, config->>'$.notifications.push' AS push_notifyFROM user_config;-- 3. 提取数组元素(按索引,从0开始)SELECT user_id, config->>'$.favorite_modules[0]' AS first_module -- 提取第一个收藏模块FROM user_config;-- 4. 条件查询(筛选theme=dark的用户)SELECT user_id, config FROM user_config WHERE config->>'$.theme' = 'dark';-- 5. 筛选数组包含指定元素的记录(JSON_CONTAINS)SELECT user_id, config FROM user_config WHERE JSON_CONTAINS(config->'$.favorite_modules', '"order"'); -- 数组元素需加引号四、进阶操作:修改、删除JSON数据
无需更新整个JSON字符串,可通过专用函数精准修改/删除指定字段,避免全量更新的性能损耗。
4.1 核心修改函数
函数 | 作用 | 示例(给user_id=1001新增sms通知) |
JSON_SET() | 新增/替换字段(存在则替换,不存在则新增) | JSON_SET(config, '$.notifications.sms', true) |
JSON_INSERT() | 仅新增字段(存在则不修改) | JSON_INSERT(config, '$.notifications.sms', true) |
JSON_REPLACE() | 仅替换字段(不存在则不操作) | JSON_REPLACE(config, '$.theme', 'light') |
JSON_REMOVE() | 删除指定字段 | JSON_REMOVE(config, '$.notifications.email') |
4.2 实操示例
-- 1. 新增字段:给1001用户新增"font_size"配置(不存在则新增)UPDATE user_config SET config = JSON_SET(config, '$.font_size', 16) WHERE user_id = 1001;-- 2. 替换字段:将1001用户的theme改为light(存在则替换)UPDATE user_config SET config = JSON_REPLACE(config, '$.theme', 'light') WHERE user_id = 1001;-- 3. 删除字段:删除1001用户的email通知配置UPDATE user_config SET config = JSON_REMOVE(config, '$.notifications.email') WHERE user_id = 1001;-- 4. 修改数组:给1001用户的favorite_modules新增"settings"(JSON_ARRAY_APPEND)UPDATE user_config SET config = JSON_ARRAY_APPEND(config, '$.favorite_modules', 'settings') WHERE user_id = 1001;-- 验证修改结果SELECT user_id, config->>'$.font_size', config->>'$.theme', config->>'$.favorite_modules' FROM user_config WHERE user_id = 1001;五、JSON索引优化:解决查询性能问题
直接对JSON字段的内部属性查询(如config->>'$.theme' = 'dark'),默认会走全表扫描,大表中性能极差。MySQL提供两种索引方案,解决JSON查询的性能问题。
5.1 方案1:虚拟列+普通索引(5.7+通用,推荐)
这是5.7+版本最稳定的方案:先基于JSON字段的属性创建生成列(虚拟列),再给虚拟列创建索引。
实操步骤
-- 1. 给user_config表新增虚拟列(基于config->>'$.theme')ALTER TABLE user_config ADD COLUMN theme VARCHAR(20) GENERATED ALWAYS AS (config->>'$.theme') STORED;-- 说明:STORED表示虚拟列物理存储(占用磁盘),可创建索引;VIRTUAL表示仅计算(不占磁盘),5.7不支持索引,8.0支持。-- 2. 给虚拟列创建索引CREATE INDEX idx_json_theme ON user_config(theme);-- 3. 查询:使用虚拟列,触发索引(EXPLAIN可验证)EXPLAINSELECT user_id, config FROM user_config WHERE theme = 'dark'; -- 用虚拟列查询,而非直接查JSON字段5.2 方案2:JSON列直接索引(8.0.17+)
MySQL 8.0.17+支持Multi-Valued Indexes,可直接对JSON数组/对象创建索引,无需虚拟列,语法更简洁。
实操示例(对数组字段创建索引)
-- 1. 给favorite_modules数组创建索引(8.0.17+)CREATE INDEX idx_json_favorite_modules ON user_config((CAST(config->'$.favorite_modules' AS CHAR(255) ARRAY)));-- 2. 索引查询:筛选favorite_modules包含"order"的用户EXPLAINSELECT user_id, config FROM user_config WHERE JSON_CONTAINS(config->'$.favorite_modules', '"order"');索引优化核心原则
- 优先给高频查询的JSON属性创建索引,避免给所有属性建索引(增加DML开销);
- 5.7版本用“虚拟列+STORED”,8.0.17+可直接用JSON数组/对象索引;
- 用EXPLAIN验证索引是否生效(key列显示索引名,type列非ALL)。
六、常用JSON函数速查
6.1 提取类(查询数据)
函数 | 作用
| 示例 |
JSON_EXTRACT(json, path) | 提取指定路径的JSON值 | JSON_EXTRACT(config, '$.theme') |
json->path | 等价于JSON_EXTRACT | config->'$.theme' |
json->>path | 提取值并去除引号 | config->>'$.theme' |
JSON_KEYS(json) | 获取JSON对象的所有键名 | JSON_KEYS(config->'$.notifications') |
6.2 修改类(更新/删除数据)
函数 | 作用 |
JSON_SET() | 新增/替换字段 |
JSON_INSERT() | 仅新增字段(存在则忽略) |
JSON_REPLACE() | 仅替换字段(不存在则忽略) |
JSON_REMOVE() | 删除指定字段 |
JSON_ARRAY_APPEND() | 给数组尾部追加元素 |
JSON_ARRAY_INSERT() | 给数组指定位置插入元素 |
6.3 校验/判断类
函数 | 作用 |
JSON_VALID(str) | 判断字符串是否为合法JSON |
JSON_CONTAINS(json, val) | 判断JSON是否包含指定值 |
JSON_CONTAINS_PATH(json, one/all, path) | 判断是否存在指定路径 |
JSON_TYPE(json) | 获取JSON值的类型(如OBJECT、ARRAY) |
七、实战场景:用户配置全流程操作
结合以上知识点,完整实现“用户配置的查询、新增、修改、筛选”全流程,模拟生产环境的实际使用:
-- 1. 新增用户配置INSERT INTO user_config (user_id, config) VALUES(1003, '{ "theme": "dark", "language": "zh-CN", "notifications": {"push": true, "email": true}, "favorite_modules": ["order", "analytics", "settings"], "font_size": 14}');-- 2. 查询1003用户的所有配置(格式化输出,更易读)SELECT user_id, JSON_PRETTY(config) AS config_detail -- JSON_PRETTY:格式化JSON输出FROM user_config WHERE user_id = 1003;-- 3. 筛选所有theme=dark且开启push通知的用户SELECT user_id, config->>'$.language' FROM user_config WHERE config->>'$.theme' = 'dark' AND config->>'$.notifications.push' = 'true';-- 4. 批量修改:将所有zh-CN语言的用户font_size改为16(无则新增)UPDATE user_config SET config = JSON_SET(config, '$.font_size', 16) WHERE config->>'$.language' = 'zh-CN';-- 5. 删除:删除1003用户的font_size配置UPDATE user_config SET config = JSON_REMOVE(config, '$.font_size') WHERE user_id = 1003;八、避坑要点:生产使用JSON的关键注意事项
JSON类型虽灵活,但使用不当会导致性能问题或数据错误,以下是生产环境必须避开的坑:
1. 避免存储超大JSON数据
JSON字段建议控制在10KB以内,超大JSON(如100KB+)会增加存储和查询开销,建议拆分:核心属性用JSON,大字段(如富文本、二进制数据)用单独的TEXT/BLOB字段。
2. 不要过度依赖JSON的灵活性
JSON适合存储非核心、多变的属性,核心业务字段(如user_id、order_no、amount)仍需用普通数据类型(INT、VARCHAR),便于索引和统计。
3. 虚拟列选择STORED而非VIRTUAL(5.7版本)
5.7版本中,VIRTUAL类型的虚拟列无法创建索引,必须用STORED(物理存储);8.0版本支持VIRTUAL列索引,可根据需求选择(VIRTUAL不占磁盘,STORED查询更快)。
4. JSON字段的NULL处理
JSON字段设置为NOT NULL时,插入NULL会报错,需插入空JSON({}或[])而非NULL:
-- 正确:插入空JSON对象INSERT INTO user_config (user_id, config) VALUES (1004, '{}');-- 错误:插入NULL(config字段设为NOT NULL)INSERT INTO user_config (user_id, config) VALUES (1004, NULL);5. 避免在JSON字段上做聚合查询
对JSON内部属性做COUNT/SUM/AVG等聚合操作,性能极差(无法利用索引),建议将需要聚合的属性抽为普通列。
6. 注意字符编码
JSON字段存储中文时,表的字符集必须为utf8mb4(否则会乱码),创建表时需显式指定。
7. 函数使用误区:数组查询需加引号
用JSON_CONTAINS查询数组时,匹配值必须加双引号(JSON字符串格式):
-- 正确JSON_CONTAINS(config->'$.favorite_modules', '"order"');-- 错误(无引号,匹配失败)JSON_CONTAINS(config->'$.favorite_modules', 'order');8. 避免频繁修改JSON字段
每次修改JSON字段,MySQL会重新序列化整个JSON二进制数据,频繁修改(如每秒数十次)会增加IO开销,建议批量修改。
九、最佳实践:何时用/何时不用JSON?
推荐使用JSON的场景
- 数据结构多变,需快速迭代(如电商商品的个性化属性);
- 存储非核心的配置/日志数据,无需复杂查询;
- 临时存储第三方接口返回数据,后续可按需解析;
- 单条记录的附属属性,无需关联多张表。
不推荐使用JSON的场景
- 核心业务字段(如订单金额、用户手机号),需精准查询和索引;
- 需要频繁聚合/统计的字段(如销量、金额);
- 数据结构固定,且需长期存储、高频查询;
- 超大文本/二进制数据(如图片、长文本)
在实际业务中,建议先小范围验证JSON类型的性能和兼容性,再逐步推广。
