如何不改表结构动态扩展字段?

开发 后端 MySQL
笔者的动态字段扩展解决方案主要针对 Mysql 5.7.8 以下版本,在 Mysql 5.7.8 已经新增 JSON Data Type,同样适用该方案,而且情况变得更加简单。

[[408268]]

 痛点

软件行业唯一不变的就是变化,比如功能上线之后,客户或 PM 需要对已有的功能增加一些合理的需求,完成这些工作必须通过添加字段解决,或者某些功能的实现需要通过增加字段来降低实现的复杂性等等。

这些问题都会改动线上的数据库表结构,一旦改动就会导致锁表,会使所有的写入操作一直等待,直到表锁关闭,特别是对于数据量大的热点表,添加一个字段可能会因为锁表时间过长而导致部分请求超时,这可能会对企业间接造成经济上的损失。

解决方案

增加 json 格式的扩展字段。

下面配合一些代码来描述这个解决方案,读者便于去理解。另外,MySQL 系列面试题和答案全部整理好了,微信搜索Java技术栈,在后台发送:面试,可以在线阅读。

mysql 数据库脚本: 

  1. DROP TABLE IF EXISTS `cs_dustbin`;  
  2. CREATE TABLE IF NOT EXISTS `cs_dustbin` (  
  3.   `id` VARCHAR(45) NOT NULL COMMENT '主键自增id',  
  4.   `rfid_no` VARCHAR(20) NOT NULL COMMENT 'rfid 卡号',  
  5.   `state` INT(1) NOT NULL COMMENT '垃圾桶状态:0:已注销;1:未使用;2:待使用;3:已使用(绑定收集点);',  
  6.   `user_id` INT NOT NULL COMMENT '登记人,负责录入垃圾桶的人',  
  7.   `type` INT(1) NOT NULL DEFAULT 1 COMMENT '垃圾桶类型:1:餐厨垃圾桶',  
  8.   `street_code` INT(11) DEFAULT NULL COMMENT '所在镇街 code,根据状态,这里的含义可能是领用镇街、退还镇街。',  
  9.   `create_time` DATETIME NOT NULL DEFAULT now() COMMENT '创建时间',  
  10.   `update_time` DATETIME NOT NULL DEFAULT now() COMMENT '更新时间',  
  11.   `ext` VARCHAR(1000) NOT NULL DEFAULT '{}' COMMENT '扩展字段',  
  12.   ...  
  13.   PRIMARY KEY (`id`))  
  14. ENGINE = InnoDB  
  15. COMMENT = '垃圾桶表'

Java 代码: 

  1. import com.alibaba.fastjson.JSON;  
  2. import lombok.Data;  
  3. import javax.validation.constraints.NotNull;  
  4. import java.util.Date;  
  5. import java.util.List;  
  6. /**  
  7.  * 垃圾桶实体  
  8.  * Created by Blink on 6/28/2018 AD.  
  9.  *  
  10.  * @author Blink  
  11.  */  
  12. @Data  
  13. public class Dustbin {  
  14.     private String id;  
  15.     /**  
  16.      * rfid 卡号  
  17.      */  
  18.     @NotNull  
  19.     private String rfidNo;  
  20.     /**  
  21.      * 垃圾桶状态:0:已注销;1:未使用;2:待使用;3:已使用(绑定收集点);  
  22.      * 对应 Dustbin.StateEnum 类  
  23.      */  
  24.     @NotNull  
  25.     private Integer state;  
  26.     /**  
  27.      * 录入垃圾桶的人员id  
  28.      */  
  29.     @NotNull  
  30.     private Long userId;  
  31.     /**  
  32.      * 垃圾桶类型:1:餐厨垃圾桶  
  33.      * DefaultValue: 1  
  34.      */  
  35.     @NotNull  
  36.     private Integer type; 
  37.     /**  
  38.      * 所在镇街 code  
  39.      * 根据状态,这里的含义可能是领用镇街、退还镇街  
  40.      */  
  41.     private Integer streetCode;  
  42.     /**  
  43.      * 创建时间  
  44.      * defaultValue : now()  
  45.      */  
  46.     @NotNull  
  47.     private Date createTime;  
  48.     /**  
  49.      * 更新时间  
  50.      */  
  51.     @NotNull  
  52.     private Date updateTime;  
  53.     /**  
  54.      * 扩展字段,详细数据查看 DustbinExt.java  
  55.      * DefaultValue: {}  
  56.      */  
  57.     private String ext;  
  58.     ...  
  59.     public DustbinExt getExtObject() {  
  60.         return JSON.parseObject(this.getExt(), DustbinExt.class);  
  61.     }  
  62.     public void setExtObject(DustbinExt ext) {  
  63.         this.ext = JSON.toJSONString(ext);  
  64.     }  
  65.     /**  
  66.      * 垃圾桶扩展属性  
  67.      * Created by Blink on 6/28/2018 AD. 
  68.      *  
  69.      * @author Blink  
  70.      */  
  71.     @Data  
  72.     public static class DustbinExt {  
  73.         /**  
  74.          * 所在镇街  
  75.          * 根据状态,这里的含义可能是领用镇街、退还镇街、绑定的镇街  
  76.          */  
  77.         private String street;  
  78.         /**  
  79.          * 客户(收集点)id,绑定收集点的时候需要填入  
  80.          * 根据目前的需求(2018-06-29),当收集点解绑的时候  
  81.          * 需要保存垃圾桶最新绑定收集点名称,所以在解绑垃圾桶的时候不会把这个信息删掉  
  82.          * 只有当绑定收集点的时候才把他覆盖  
  83.          */  
  84.         private Long customerId;  
  85.         /**  
  86.          * 客户(收集点)名称,绑定收集点的时候需要填入  
  87.          * 根据目前的需求(2018-06-29),当收集点解绑的时候  
  88.          * 需要保存垃圾桶最新绑定收集点名称,所以在解绑垃圾桶的时候不会把这个信息删掉  
  89.          * 只有当绑定收集点的时候才把他覆盖  
  90.          */  
  91.         private String customer;   
  92.         /**  
  93.          * 损坏部位  
  94.          * 1:桶盖;2:桶口;3:桶身;4:桶轴;5:桶底;6:桶轮;  
  95.          * 对应 DustbinDamagePartEnum 类  
  96.          */  
  97.         private List<Integer> parts;  
  98.     }  
  99.     ... 
  100.  

mysql 脚本可以看到扩展字段的信息:

  1. ext VARCHAR(1000) NOT NULL DEFAULT '{}' COMMENT '扩展字段' 

可以看到这么一段 Java 代码: 

  1. ...  
  2. /**  
  3.  * 扩展字段,详细字段查看 DustbinExt 类  
  4.  * DefaultValue: {}  
  5.  */  
  6. private String ext;  
  7. public DustbinExt getExtObject() {  
  8.     return JSON.parseObject(this.getExt(), DustbinExt.class);  
  9.  
  10. public void setExtObject(DustbinExt ext) {  
  11.     this.ext = JSON.toJSONString(ext);  
  12.  
  13. ... 

可以看到 ext 字段就是用来存储 json 格式的数据,它可以动态地增加任何字段,甚至是对象,不需要通过 DDL(Data Definition Language) 去创建字段,非常适合用来解决上面提到的问题。

Java 代码在这里起到辅助性作用,通过定义一个内部类来管理扩展字段的属性,方便我们了解和管理扩展字段,提高代码的可读性和可维护性,java 这种方式也是笔者总结出来的较为优雅的做法(个人观点)。

局限性

有经验的读者可能会提出,ext 字段在 Mysql 5.7.8 以下版本无法对扩展字段中的某一个或一部分字段建立索引,因为 Mysql 5.7.8 版本以下不支持(Mysql 5.7.8 支持为 Json Data Type 建立索引)。MySQL数据库开发的 36 条军规!

没错,这是这个解决方案的一个局限性,在 Mysql 5.7.8 以下版本,我的建议是, ext 扩展字段不要存储热点数据,只存储非热点数据,这样就可以避免查询操作,降低维护 ext 字段带来的成本和风险,那如何识别新增字段是不是热点数据呢?

这个需要结合实际业务需求来判断,也可以询问对业务和技术更有经验的同事,便于读者更快得出结论。

终极版解决方案

在一些极端的情况下,变化可能来得太快,而我们要的是减少变化带来的成本和风险,所以在表设计之初可以根据自身经验,或者找更有经验的人寻求帮助,预估一下需要预留多少个备用字段,再配合扩展字段,基本上可以把改变(添加字段)表结构的次数降至一个非常少的次数。

总结

在特殊情况下,通过扩展字段 + 预留字段基本上可以做到动态扩展字段,又不会影响为热点数据建立索引的情况,这样我们得到了一个非常灵活的表结构,便于我们应对未来的变化,但是请注意,要维护好我们的实体,包括里面的每一个字段,敬畏每一行代码。

最后,关注公众号Java技术栈,在后台回复:面试,可以获取我整理的 MySQL 系列面试题和答案,非常齐全。 

 

责任编辑:庞桂玉 来源: Java技术栈
相关推荐

2010-09-16 15:56:15

SQL Server表

2010-10-13 10:03:08

MySQL修改表结构

2021-01-04 08:24:44

Class字段表方法表

2015-08-18 13:25:12

PaaS服务架构动态扩展

2012-06-14 10:14:46

ibmdw

2020-08-07 07:58:24

Oracle框架数据库

2011-01-20 17:44:25

2023-03-21 08:41:09

结构设计数据库高性能

2010-06-01 09:20:45

jQuery

2010-03-17 10:17:37

Java

2022-09-02 15:38:06

KubernetesYAML

2010-09-28 14:52:37

SQL表结构

2015-09-24 08:54:36

java动态代理

2015-09-24 08:55:14

Java动态代理扩展

2010-11-24 10:35:34

MySQL单表多字段

2011-04-07 14:04:28

SQL动态交叉表

2010-05-24 14:38:41

MySQL数据库

2010-04-21 09:26:54

Java动态代理

2022-10-08 14:44:01

VSCode开源

2022-07-01 17:57:45

KubernetesAPI
点赞
收藏

51CTO技术栈公众号