暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

基于JSON的Mysql可扩展性设计

新架构思考 2021-11-15
2346


目标:实现自定义字段,字段的内容是可扩展的,高性能查询,高扩展性(功能扩展)

1.比如项目管理中一个项目涉及到多个参与人,可以对项目打标签(不用多表关联的方式,采用多表关联要求一个项目对应多个相关人,是一对多的关系),将项目和标签的数据都存储在一个项目表里面,降低多表关联,提高查询性能。

2.项目又分不同类型,不同类型对应的扩展字段存储的内容不一样,比如研发类型的项目,测试类型的项目,每一个都会有一些特有的扩展内容。

扩展字段比如叫extValue,存储内容不一样。

 

一、通常的做法是:

查看目前mysql版本:

select version();

环境

版本

uat

5.7.28-log

sit

5.7.28-log

pre

5.7.19-1-log

prod

5.7.19-1-log

8.0 使用json-partial-updates
特性来提高json的更新效率, 也就是只更新指定的json字段, 其它字段不变.而5.7版本, 会更新整个json. 生产环境强烈建议使用8.0版本。

https://cloud.tencent.com/developer/article/1843196

JSON特点

1.可以使用json中的字段做join(JSON Table Functions);

2.json长度不固定,不适合存储更新需求较多的场景;8.0 json性能好:8.0.17有Multi-Valued Indexes,就比5.7版本好很多

 

通常实现的的方式,(单表,TEXT存储)

extValue, TEXT类型

[{"id":"xxxxx","realName":"高*","userName":"高*","userAccount":"gaolaozhuang"},{"id":"yyyyyy":"测***","userName":"测***","userAccount":"135*VA*3312"}]

 

优点:单表查询,支持自定义扩展内容;缺点把整个字段作为一个字符串,需要解析处理,无法支持根据JSON里面的内容为条件建立索引来查询,根据某一个参与人为条件获取该参与人所有任务列表。

 

多表关联的实现方式

缺点:结构固定,有新的扩展字段需求需要改代码,加新字段,或者预留extValue扩展字段方式。

 

Mysql JSON方式的优点:

MySQL5.7中就新增加了一个数据类型JSON,使用mysql的json类型字段做扩展字段,可以以json串形式动态的存储任意结构的数据,包括list结构的数据也不必再创建子表。代码的实体类和Dao层不必修改,其他层代码修改量也能够减少没有JSON数据类型的支持,我们一样可以通过varchar类型或者text等类型来保存这一格式的数据。

JSON 类型的另一个好处是无须预定义字段,字段可以无限扩展。而传统关系型数据库的列都需预先定义,想要扩展需要执行 ALTER TABLE ... ADD COLUMN ... 这样比较重的操作(或者提前预留扩展字段方式,但是预留的字段的含义无法事先确定)


参考Mysql8.0官网内容:

https://dev.mysql.com/doc/refman/8.0/en/json.html

 

JSON对象除了支持字符串、整型、日期类型,JSON 内嵌的字段也支持数组类型。

 

字段类型处理器(TypeHandler)

MyBatis 中的 TypeHandler 类型处理器用于 JavaType 与 JdbcType 之间的转换

通过 @TableField 注解将 FastjsonTypeHandler 这个类型处理器快速注入到 mybatis 容器中

注意:使用字段类型处理器时,必须开启映射注解 @TableName(autoResultMap = true)。否则插入没问题,但查询时该字段会为空

 

 

自定义类型处理器

https://note.youdao.com/ynoteshare/index.html?id=b5dcac49d70bda88bee06741493e0c6e&type=note&_time=1632899105427

自定义的 TypeHandler 类型处理器,则需继承 ListTypeHandler 接口,接口作用是用于指定 jdbc 与 java 的数据类型间对应关系处理。接口代码如下:

1

2

3

4

5

6

7

8

public interface TypeHandler<T> {

  // 保存操作,数据入库之前时数据处理

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  //下面三个则是,从数据库加载数据后,vo对象封装前的数据处理

  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

 

 

 

MySQL 8.0.17 版本开始支持 Multi-Valued Indexes,用于在 JSON 数组上创建索引,并通过函数 member of、json_contains、json_overlaps 来快速检索索引数据。所以你可以在表 UserTag 上创建 Multi-Valued Indexes

例子:

 

CREATE TABLE `extra_info` (

 `id` int(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,

 `extra_object` json NULL,

 `extra_list` json NULL,

 `extra_array` json NULL

);

 

SELECT * FROM extra_info.extra_info;

DROP TABLE IF EXISTS UserLogin;

 

CREATE TABLE UserLogin (

    userId BIGINT NOT NULL,

    loginInfo JSON,

    PRIMARY KEY(userId)

);

INSERT INTO `extra_info` VALUES (1, '{\"id\": 1, \"name\": \"2\"}', '[{\"id\": 1, \"name\": \"2\"}]', '[{\"id\": 1, \"name\": \"2\"}]');

 

 

SET @a = '

{

   "cellphone" : "13918888888",

   "wxchat" : "破产码农",

   "QQ" : "82946772"

}

';

 

INSERT INTO UserLogin1(userId,loginInfo) VALUES (1,@a);

 

SET @b = '

{  

  "cellphone" : "15026888888"

}

';

 

INSERT INTO UserLogin1(userId,loginInfo) VALUES (2,@b);

 

 

SELECT

    userId,

    JSON_UNQUOTE(JSON_EXTRACT(loginInfo,"$.cellphone")) cellphone,

    JSON_UNQUOTE(JSON_EXTRACT(loginInfo,"$.wxchat")) wxchat

FROM UserLogin;

 

 

 

SELECT 

    userId,

    loginInfo->>"$.cellphone" cellphone,

    loginInfo->>"$.wxchat" wxchat,

      loginInfo->>"$.QQ" qq

FROM UserLogin;

 

 

 

ALTER TABLE UserLogin ADD COLUMN cellphone VARCHAR(255) AS (loginInfo->>"$.cellphone");

 

ALTER TABLE UserLogin ADD UNIQUE INDEX idx_cellphone(cellphone);

 

 

EXPLAIN SELECT  *  FROM UserLogin 

WHERE cellphone = '13918888888';

 

 

CREATE TABLE UserLogin1 (

    userId BIGINT,

    loginInfo JSON,

    cellphone VARCHAR(255) AS (loginInfo->>"$.cellphone"),

    PRIMARY KEY(userId),

    UNIQUE KEY uk_idx_cellphone(cellphone)

);

 

 

CREATE TABLE `usertag` (

  `userId` bigint NOT NULL,

  `userTags` json DEFAULT NULL,

  PRIMARY KEY (`userId`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

 

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]');

 

ALTER TABLE UserTag

ADD INDEX idx_user_tags ((cast((userTags->"$"as unsigned array)));

 

EXPLAIN SELECT * FROM UserTag 

WHERE 10 MEMBER OF(userTags->"$");

 

查询用户画像为常看电影的用户,可以使用函数 MEMBER OF:

SELECT * FROM UserTag 

WHERE 10 MEMBER OF(userTags->"$");

 

 

画像为 80 后,且常看电影的用户,可以使用函数 JSON_CONTAINS:

XPLAIN SELECT * FROM UserTag 

WHERE JSON_CONTAINS(userTags->"$"'[2,10]')

 

 

想要查询画像为 80 后、90 后,且常看电影的用户,则可以使用函数 JSON_OVERLAP:

EXPLAIN SELECT * FROM UserTag 

WHERE JSON_OVERLAPS(userTags->"$"'[2,3,10]')

 

 

  • 使用 JSON 数据类型,推荐用 MySQL 8.0.17 以上的版本,性能更好,同时也支持 Multi-Valued Indexes

  • JSON 数据类型的好处是无须预先定义列,数据本身就具有很好的描述性;

  • 不要将有明显关系型的数据用 JSON 存储,如用户余额、用户姓名、用户身份证等,这些都是每个用户必须包含的数据;

  • JSON 数据类型推荐使用在不经常更新的静态数据存储。

  • https://mp.weixin.qq.com/s/MxyYM0wdffs6V-f4vLrsBA

multi-value index是functional index的一种实现,列的定义是一个虚拟列,值是从json column上取出来的数组

数组上存在相同值的话,会只存储一个到索引上。支持的类型:DECIMAL, INTEGER, DATETIME,VARCHAR/CHAR。另外index上只能有一个multi-value column。

 

常见的可扩展性方案:

1

方案

优缺点

2

元数据表+大宽表预留字段(预留500列)

元数据表设计比较复杂,需要考虑字段之间的引用关系,主子明细关系,关联关系,字段计算。

实现复杂,对每个预留字段要进行元数据描述,包括字段的名称,类型,长度,以及该字段存储具体数据位置等,常见的apaas平台采用这种方式,可以对预留字段进行权限控制,也就是实现字段级的权限控制。业界实现的公司很少,都需要针对特定需求做一些定制化改动,无法实现完全的可配置化的实施,也就是低代码不需要改动任何代码就可以实施出来。

3

基于JSON的自定义字段扩展,依赖于mysql8.0实现

实现简单,可扩展性一般,JSON数据多的化,对JSON的修改操作也会很麻烦,业务成功的案例不是很多,因为mysql8.0才对JSON有了很好的支持,mysql5.7版本性能有一些问题。

【MySQL】对JSON数据操作(全网最全)

详细参考官网:https://dev.mysql.com/doc/refman/8.0/en/json-functions.html

 

例子:

CREATE TABLE `dept` (

  `id` int(11) NOT NULL,

  `dept` varchar(255) DEFAULT NULL,

  `json_value` json DEFAULT NULL,

  PRIMARY KEY (`id`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;

 

insert into dept VALUES(1,'部门1','{"deptName": "部门1", "deptId": "1", "deptLeaderId": "3"}');

insert into dept VALUES(2,'部门2','{"deptName": "部门2", "deptId": "2", "deptLeaderId": "4"}');

insert into dept VALUES(3,'部门3','{"deptName": "部门3", "deptId": "3", "deptLeaderId": "5"}');

insert into dept VALUES(4,'部门4','{"deptName": "部门4", "deptId": "4", "deptLeaderId": "5"}');

insert into dept VALUES(5,'部门5','{"deptName": "部门5", "deptId": "5", "deptLeaderId": "5"}');

一般基础查询操作

1、使用 json字段名->’$.json属性’ 进行查询条件

查询deptLeader=张五的数据,那么sql语句如下:

SELECT * from dept WHERE json_value->'$.deptLeaderId'='5';

 


文章转载自新架构思考,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论