作为一名长期与MySQL打交道的开发者,我发现JSON类型在实际项目中越来越常见。MySQL从5.7.8版本开始原生支持JSON数据类型,这为处理半结构化数据提供了极大便利。本文将结合我多年的实战经验,带你全面掌握MySQL中JSON类型的核心用法和性能优化技巧。
在传统关系型数据库中,我们通常需要严格定义表结构。但当遇到以下场景时,JSON类型就显示出独特优势:
我曾在电商项目中用JSON字段存储商品扩展属性,相比传统的EAV(实体-属性-值)模型,查询性能提升了3倍以上,且维护成本大幅降低。
相比将JSON存储在TEXT或VARCHAR字段中,原生JSON类型具有以下不可替代的优势:
实际案例:在某日志分析系统中,将日志从TEXT改为JSON类型后,查询响应时间从平均800ms降至120ms
创建包含JSON列的表非常简单:
sql复制CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
attributes JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
插入JSON数据有三种常用方式:
sql复制INSERT INTO products (name, attributes)
VALUES ('智能手机', '{"brand":"华为", "color":"黑色", "specs":{"ram":"8GB", "storage":"128GB"}}');
sql复制INSERT INTO products (name, attributes)
VALUES ('笔记本电脑', JSON_OBJECT(
'brand', '苹果',
'model', 'MacBook Pro',
'price', 12999,
'in_stock', TRUE
));
sql复制INSERT INTO products (name, attributes)
SELECT '平板电脑', CAST('{"brand":"小米","size":"10.1英寸"}' AS JSON);
注意事项:JSON字符串必须使用双引号,单引号会导致语法错误。在MySQL 8.0+中,可以使用
JSON_QUOTE()函数自动处理引号问题。
JSON_EXTRACT()是最常用的查询函数,但更推荐使用简化的->和->>操作符:
sql复制-- 提取整个JSON对象
SELECT attributes->'$.specs' FROM products WHERE id = 1;
-- 提取具体属性(带引号)
SELECT attributes->'$.brand' FROM products WHERE name LIKE '%手机%';
-- 提取具体属性(不带引号)
SELECT attributes->>'$.brand' FROM products WHERE name LIKE '%手机%';
JSON路径表达式是查询的核心,支持多种灵活用法:
sql复制-- 获取嵌套属性
SELECT attributes->'$.specs.ram' FROM products;
-- 数组索引访问(从0开始)
SELECT attributes->'$.features[0]' FROM products;
-- 范围查询
SELECT attributes->'$.features[1 to 3]' FROM products;
-- 通配符查询
SELECT attributes->'$.specs.*' FROM products;
sql复制-- 检查属性是否存在
SELECT name FROM products
WHERE JSON_CONTAINS_PATH(attributes, 'one', '$.specs.ram');
-- 按JSON值过滤
SELECT name FROM products
WHERE attributes->>'$.brand' = '华为';
-- 检查数组包含
SELECT name FROM products
WHERE JSON_CONTAINS(attributes->'$.tags', '"新品"');
sql复制UPDATE products SET attributes = '{"brand":"小米","color":"蓝色"}'
WHERE id = 1;
sql复制UPDATE products
SET attributes = JSON_SET(attributes, '$.color', '金色', '$.price', 5999)
WHERE id = 2;
sql复制-- 追加元素
UPDATE products
SET attributes = JSON_ARRAY_APPEND(attributes, '$.tags', '促销');
-- 插入元素
UPDATE products
SET attributes = JSON_ARRAY_INSERT(attributes, '$[0]', '热销');
-- 删除元素
UPDATE products
SET attributes = JSON_REMOVE(attributes, '$.old_price');
实战经验:部分更新比完整替换性能更好,特别是在处理大JSON文档时。在MySQL 8.0中,部分更新是原地(in-place)进行的,效率极高。
sql复制SELECT JSON_OBJECT(
'id', id,
'name', name,
'price', attributes->'$.price'
) AS product_json
FROM products;
sql复制SELECT
p.id,
p.name,
jt.brand,
jt.color
FROM products p,
JSON_TABLE(p.attributes, '$' COLUMNS (
brand VARCHAR(20) PATH '$.brand',
color VARCHAR(10) PATH '$.color'
)) AS jt;
sql复制-- 将多行合并为JSON数组
SELECT
attributes->>'$.category' AS category,
JSON_ARRAYAGG(JSON_OBJECT('id', id, 'name', name)) AS products
FROM products
GROUP BY attributes->>'$.category';
sql复制ALTER TABLE products ADD COLUMN brand VARCHAR(20)
GENERATED ALWAYS AS (attributes->>'$.brand') STORED;
CREATE INDEX idx_brand ON products(brand);
sql复制CREATE TABLE product_tags (
id INT PRIMARY KEY,
tags JSON,
INDEX idx_tags ((CAST(tags AS CHAR(20) ARRAY)))
);
-- 使用MEMBER OF查询
SELECT * FROM product_tags WHERE '新品' MEMBER OF(tags);
sql复制-- 检查JSON文档大小
SELECT id, name, JSON_STORAGE_SIZE(attributes) AS json_size
FROM products
ORDER BY json_size DESC;
性能建议:JSON文档不宜过大,超过1MB会影响性能。考虑将大JSON拆分为多个字段或关联表。
sql复制-- 错误示例
INSERT INTO products (attributes) VALUES ('{brand:"华为"}'); -- 缺少引号
-- 解决方案
INSERT INTO products (attributes) VALUES ('{"brand":"华为"}');
sql复制-- 安全访问不存在的路径
SELECT IFNULL(attributes->'$.nonexistent', 'default') FROM products;
sql复制-- 显式类型转换
SELECT CAST(attributes->'$.price' AS DECIMAL(10,2)) FROM products;
sql复制EXPLAIN SELECT * FROM products WHERE attributes->>'$.brand' = '华为';
sql复制-- 避免在WHERE条件中对JSON列使用函数
-- 不推荐(无法使用索引)
SELECT * FROM products WHERE JSON_EXTRACT(attributes, '$.brand') = '华为';
-- 推荐(可使用生成列索引)
SELECT * FROM products WHERE brand = '华为';
设计原则:
查询优化:
->>操作符替代JSON_UNQUOTE(JSON_EXTRACT())维护建议:
JSON_SCHEMA_VALID()进行数据校验(MySQL 8.0.17+)在实际项目中,我通常会为JSON字段建立完善的文档说明,包括预期的结构和示例。这大大降低了团队成员的维护成本。
在Java Spring Boot中使用MyBatis处理JSON字段:
java复制// 实体类
@Data
public class Product {
private Integer id;
private String name;
private JSONObject attributes; // 使用com.alibaba.fastjson.JSONObject
}
// Mapper接口
public interface ProductMapper {
@Select("SELECT id, name, attributes FROM products WHERE id = #{id}")
Product findById(Integer id);
@Update("UPDATE products SET attributes = #{attributes, jdbcType=OTHER} WHERE id = #{id}")
void updateAttributes(@Param("id") Integer id, @Param("attributes") String attributes);
}
当JSON数据变得过于复杂或查询需求增加时,可以考虑:
MySQL对JSON的支持仍在不断增强:
我在实际工作中发现,合理使用JSON类型可以显著简化数据结构,但必须注意不要滥用。当JSON字段变得过于复杂或查询性能下降时,应该考虑重构为传统关系模型。
最后分享一个实用技巧:在开发环境中,可以使用JSON_PRETTY()函数格式化JSON输出,便于调试和日志记录:
sql复制SELECT id, name, JSON_PRETTY(attributes) FROM products LIMIT 1;