关系型的结构化存储存在一定的弊端,因为它需要预先定义好所有的列以及列对应的类型。但是业务在发展过程中,或许需要扩展单个列的描述功能,这时,如果能用好 json 数据类型,那就能打通关系型和非关系型数据的存储之间的界限,为业务提供更好的架构选择。当然,很多同学在用 json 数据类型时会遇到各种各样的问题,其中最容易犯的误区就是将类型 json 简单理解成字符串类型。但当你看完这篇文章后,会真正认识到 json 数据类型的威力,从而在实际工作中更好地存储非结构化的数据。 json 数据类型 json(javascript object notation)主要用于互联网应用服务之间的数据交换。mysql 支持rfc 7159定义的 json 规范,主要有 json 对象 和 json 数组 两种类型。下面就是 json 对象,主要用来存储图片的相关信息: { image: { width: 800, height: 600, title: view from 15th floor, thumbnail: { url: http://www.example.com/image/481989943, height: 125, width: 100 }, ids: [116, 943, 234, 38793] } }
从中你可以看到, json 类型可以很好地描述数据的相关内容,比如这张图片的宽度、高度、标题等(这里使用到的类型有整型、字符串类型)。json对象除了支持字符串、整型、日期类型,json 内嵌的字段也支持数组类型,如上代码中的 ids 字段。另一种 json 数据类型是数组类型,如: [ { precision: zip, latitude: 37.7668, longitude: -122.3959, address: , city: san francisco, state: ca, zip: 94107, country: us }, { precision: zip, latitude: 37.371991, longitude: -122.026020, address: , city: sunnyvale, state: ca, zip: 94085, country: us } ]
上面的示例演示的是一个 json 数组,其中有 2 个 json 对象。到目前为止,可能很多同学会把 json 当作一个很大的字段串类型,从表面上来看,没有错。但本质上,json 是一种新的类型,有自己的存储格式,还能在每个对应的字段上创建索引,做特定的优化,这是传统字段串无法实现的。json 类型的另一个好处是无须预定义字段,字段可以无限扩展。而传统关系型数据库的列都需预先定义,想要扩展需要执行 alter table ... add column ... 这样比较重的操作。需要注意是,json 类型是从 mysql 5.7 版本开始支持的功能,而 8.0 版本解决了更新 json 的日志性能瓶颈。如果要在生产环境中使用 json 数据类型,强烈推荐使用 mysql 8.0 版本。讲到这儿,你已经对 json 类型的基本概念有所了解了,接下来,我们进入实战环节:如何在业务中用好json类型? 业务表结构设计实战 用户登录设计 在数据库中,json 类型比较适合存储一些修改较少、相对静态的数据,比如用户登录信息的存储如下: drop table if exists userlogin; create table userlogin ( userid bigint not null, logininfo json, primary key(userid) );
由于当前业务的登录方式越来越多样化,如同一账户支持手机、微信、qq 账号登录,所以这里可以用 json 类型存储登录的信息。接着,插入下面的数据: set @a = ' { cellphone : 13918888888, wxchat : 破产码农, qq : 82946772 } '; insert into userlogin values (1,@a); set @b = ' { cellphone : 15026888888 } '; insert into userlogin values (2,@b);
从上面的例子中可以看到,用户 1 登录有三种方式:手机验证码登录、微信登录、qq 登录,而用户 2 只有手机验证码登录。而如果不采用 json 数据类型,就要用下面的方式建表: select userid, json_unquote(json_extract(logininfo,$.cellphone)) cellphone, json_unquote(json_extract(logininfo,$.wxchat)) wxchat from userlogin; +--------+-------------+--------------+ | userid | cellphone | wxchat | +--------+-------------+--------------+ | 1 | 13918888888 | 破产码农 | | 2 | 15026888888 | null | +--------+-------------+--------------+ 2 rows in set (0.01 sec)
当然了,每次写 json_extract、json_unquote 非常麻烦,mysql 还提供了 ->> 表达式,和上述 sql 效果完全一样: select userid, logininfo->>$.cellphone cellphone, logininfo->>$.wxchat wxchat from userlogin;
当 json 数据量非常大,用户希望对 json 数据进行有效检索时,可以利用 mysql 的 函数索引 功能对 json 中的某个字段进行索引。比如在上面的用户登录示例中,假设用户必须绑定唯一手机号,且希望未来能用手机号码进行用户检索时,可以创建下面的索引: alter table userlogin add column cellphone varchar(255) as (logininfo->>$.cellphone); alter table userlogin add unique index idx_cellphone(cellphone);
上述 sql 首先创建了一个虚拟列 cellphone,这个列是由函数 logininfo->>$.cellphone 计算得到的。然后在这个虚拟列上创建一个唯一索引 idx_cellphone。这时再通过虚拟列 cellphone 进行查询,就可以看到优化器会使用到新创建的 idx_cellphone 索引: explain select * from userlogin where cellphone = '13918888888'g *************************** 1. row *************************** id: 1 select_type: simple table: userlogin partitions: null type: const possible_keys: idx_cellphone key: idx_cellphone key_len: 1023 ref: const rows: 1 filtered: 100.00 extra: null 1 row in set, 1 warning (0.00 sec)
当然,我们可以在一开始创建表的时候,就完成虚拟列及函数索引的创建。如下表创建的列 cellphone 对应的就是 json 中的内容,是个虚拟列;uk_idx_cellphone 就是在虚拟列 cellphone 上所创建的索引。 create table userlogin ( userid bigint, logininfo json, cellphone varchar(255) as (logininfo->>$.cellphone), primary key(userid), unique key uk_idx_cellphone(cellphone) );
用户画像设计 某些业务需要做用户画像(也就是对用户打标签),然后根据用户的标签,通过数据挖掘技术,进行相应的产品推荐。这份架构师图谱建议看看,少走弯路。比如: 在电商行业中,根据用户的穿搭喜好,推荐相应的商品; 在音乐行业中,根据用户喜欢的音乐风格和常听的歌手,推荐相应的歌曲; 在金融行业,根据用户的风险喜好和投资经验,推荐相应的理财产品。 在这,我强烈推荐你用 json 类型在数据库中存储用户画像信息,并结合 json 数组类型和多值索引的特点进行高效查询。假设有张画像定义表: create table tags ( tagid bigint auto_increment, tagname varchar(255) not null, primary key(tagid) ); select * from tags; +-------+--------------+ | tagid | tagname | +-------+--------------+ | 1 | 70后 | | 2 | 80后 | | 3 | 90后 | | 4 | 00后 | | 5 | 爱运动 | | 6 | 高学历 | | 7 | 小资 | | 8 | 有房 | | 9 | 有车 | | 10 | 常看电影 | | 11 | 爱网购 | | 12 | 爱外卖 | +-------+--------------+
可以看到,表 tags 是一张画像定义表,用于描述当前定义有多少个标签,接着给每个用户打标签,比如用户 david,他的标签是 80 后、高学历、小资、有房、常看电影;用户 tom,90 后、常看电影、爱外卖。若不用 json 数据类型进行标签存储,通常会将用户标签通过字符串,加上分割符的方式,在一个字段中存取用户所有的标签: +-------+---------------------------------------+ |用户 |标签 | +-------+---------------------------------------+ |david |80后 ; 高学历 ; 小资 ; 有房 ;常看电影 | |tom |90后 ;常看电影 ; 爱外卖 | +-------+---------------------------------------
这样做的缺点是:不好搜索特定画像的用户,另外分隔符也是一种自我约定,在数据库中其实可以任意存储其他数据,最终产生脏数据。用 json 数据类型就能很好解决这个问题: drop table if exists usertag; create table usertag ( userid bigint not null, usertags json, primary key (userid) ); insert into usertag values (1,'[2,6,8,10]'); insert into usertag values (2,'[3,10,12]');
其中,usertags 存储的标签就是表 tags 已定义的那些标签值,只是使用 json 数组类型进行存储。另外,mysql 系列面试题和答案全部整理好了,微信搜索java技术栈,在后台发送:面试,可以在线阅读。mysql 8.0.17 版本开始支持 multi-valued indexes,用于在 json 数组上创建索引,并通过函数 member of、json_contains、json_overlaps 来快速检索索引数据。所以你可以在表 usertag 上创建 multi-valued indexes: alter table usertag add index idx_user_tags ((cast((usertags->$) as unsigned array)));
如果想要查询用户画像为常看电影的用户,可以使用函数 member of: explain select * from usertag where 10 member of(usertags->$)g *************************** 1. row *************************** id: 1 select_type: simple table: usertag partitions: null type: ref possible_keys: idx_user_tags key: idx_user_tags key_len: 9 ref: const rows: 1 filtered: 100.00 extra: using where 1 row in set, 1 warning (0.00 sec) select * from usertag where 10 member of(usertags->$); +--------+---------------+ | userid | usertags | +--------+---------------+ | 1 | [2, 6, 8, 10] | | 2 | [3, 10, 12] | +--------+---------------+ 2 rows in set (0.00 sec)
如果想要查询画像为 80 后,且常看电影的用户,可以使用函数 json_contains: explain select * from usertag where json_contains(usertags->$, '[2,10]')g *************************** 1. row *************************** id: 1 select_type: simple table: usertag partitions: null type: range possible_keys: idx_user_tags key: idx_user_tags key_len: 9 ref: null rows: 3 filtered: 100.00 extra: using where 1 row in set, 1 warning (0.00 sec) select * from usertag where json_contains(usertags->$, '[2,10]'); +--------+---------------+ | userid | usertags | +--------+---------------+ | 1 | [2, 6, 8, 10] | +--------+---------------+ 1 row in set (0.00 sec)
如果想要查询画像为 80 后、90 后,且常看电影的用户,则可以使用函数 json_overlap: explain select * from usertag where json_overlaps(usertags->$, '[2,3,10]')g *************************** 1. row *************************** id: 1 select_type: simple table: usertag partitions: null type: range possible_keys: idx_user_tags key: idx_user_tags key_len: 9 ref: null rows: 4 filtered: 100.00 extra: using where 1 row in set, 1 warning (0.00 sec) select * from usertag where json_overlaps(usertags->$, '[2,3,10]'); +--------+---------------+ | userid | usertags | +--------+---------------+ | 1 | [2, 6, 8, 10] | | 2 | [3, 10, 12] | +--------+---------------+ 2 rows in set (0.01 sec)
总结 json 类型是 mysql 5.7 版本新增的数据类型,用好 json 数据类型可以有效解决很多业务中实际问题。最后,我总结下今天的重点内容: 使用 json 数据类型,推荐用 mysql 8.0.17 以上的版本,性能更好,同时也支持 multi-valued indexes; json 数据类型的好处是无须预先定义列,数据本身就具有很好的描述性; 不要将有明显关系型的数据用 json 存储,如用户余额、用户姓名、用户身份证等,这些都是每个用户必须包含的数据; json 数据类型推荐使用在不经常更新的静态数据存储。 来源:https://blog.csdn.net/java_pfx/article/details/116594654
-end-
稳压二极管的温度系数
电动牙刷哪个牌子好?500元内最好用的牙刷品牌推荐
手机的大脑——主流处理器的盘点
六款热门汽车的智能座舱功能
美禁止政府合同方使用华为、海康威视、大华、海能达以及中兴产品
真正认识到JSON数据类型的威力
OPPOR9s、vivoX9对比评测:目前不到2600元,价格配置差不多怎么选?
区块链和大数据能够开启新篇章?
东芝推出无需传感器控制采用闭环转速控制技术的新型三相无刷电机控制预驱IC
边界条件复制/真实曲面网格设置/磁滞模型定义
数字家电产品的发展趋势与兼容性问题的研究
恩智浦战略布局自动驾驶,收购OmniPHY
上海大革全球首发新型全碳化硅模块快速充电桩
安全地毯控制器的作用有哪些?
对比两个基于Redis下的存储方案在性能方面的优劣
通富微电:全力支持客户5nm产品导入 现已完成研发逐步量产
华为Mate10什么时候上市?华为Mate10最新消息:麒麟970+6GB+徕卡3代,全面升级依旧后置指纹
vivoNEX红黑双色高清图赏
苹果官网进行更新悄悄地推出了两款新品:新款 iPad Air 和 iPad mini
多节锂电池充电管理方案-TPS54201在扫地机器人的应用