zoukankan      html  css  js  c++  java
  • 如何不改表结构动态扩展字段?

    作者:李瑜宁

    来源:https://juejin.cn/post/6844903640990220302

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

    痛点

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

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

    解决方案

    增加 json 格式的扩展字段。

    下面配合一些代码来描述这个解决方案,读者便于去理解。

    mysql 数据库脚本:

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

    Java 代码:

    import com.alibaba.fastjson.JSON;
    import lombok.Data;
    
    import javax.validation.constraints.NotNull;
    import java.util.Date;
    import java.util.List;
    
    /**
     * 垃圾桶实体
     * Created by Blink on 6/28/2018 AD.
     *
     * @author Blink
     */
    @Data
    public class Dustbin {
    
        private String id;
    
        /**
         * rfid 卡号
         */
        @NotNull
        private String rfidNo;
    
        /**
         * 垃圾桶状态:0:已注销;1:未使用;2:待使用;3:已使用(绑定收集点);
         * 对应 Dustbin.StateEnum 类
         */
        @NotNull
        private Integer state;
    
        /**
         * 录入垃圾桶的人员id
         */
        @NotNull
        private Long userId;
    
        /**
         * 垃圾桶类型:1:餐厨垃圾桶
         * DefaultValue: 1
         */
        @NotNull
        private Integer type;
    
        /**
         * 所在镇街 code
         * 根据状态,这里的含义可能是领用镇街、退还镇街
         */
        private Integer streetCode;
    
        /**
         * 创建时间
         * defaultValue : now()
         */
        @NotNull
        private Date createTime;
    
        /**
         * 更新时间
         */
        @NotNull
        private Date updateTime;
    
        /**
         * 扩展字段,详细数据查看 DustbinExt.java
         * DefaultValue: {}
         */
        private String ext;
    
        ...
    
        public DustbinExt getExtObject() {
            return JSON.parseObject(this.getExt(), DustbinExt.class);
        }
    
        public void setExtObject(DustbinExt ext) {
            this.ext = JSON.toJSONString(ext);
        }
    
        /**
         * 垃圾桶扩展属性
         * Created by Blink on 6/28/2018 AD.
         *
         * @author Blink
         */
        @Data
        public static class DustbinExt {
    
            /**
             * 所在镇街
             * 根据状态,这里的含义可能是领用镇街、退还镇街、绑定的镇街
             */
    
            private String street;
    
            /**
             * 客户(收集点)id,绑定收集点的时候需要填入
             * 根据目前的需求(2018-06-29),当收集点解绑的时候
             * 需要保存垃圾桶最新绑定收集点名称,所以在解绑垃圾桶的时候不会把这个信息删掉
             * 只有当绑定收集点的时候才把他覆盖
             */
            private Long customerId;
    
            /**
             * 客户(收集点)名称,绑定收集点的时候需要填入
             * 根据目前的需求(2018-06-29),当收集点解绑的时候
             * 需要保存垃圾桶最新绑定收集点名称,所以在解绑垃圾桶的时候不会把这个信息删掉
             * 只有当绑定收集点的时候才把他覆盖
             */
            private String customer;
    
            /**
             * 损坏部位
             * 1:桶盖;2:桶口;3:桶身;4:桶轴;5:桶底;6:桶轮;
             * 对应 DustbinDamagePartEnum 类
             */
            private List<Integer> parts;
        }
    
        ...
    }
    

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

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

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

    ...
    
    /**
     * 扩展字段,详细字段查看 DustbinExt 类
     * DefaultValue: {}
     */
    private String ext;
    
    public DustbinExt getExtObject() {
        return JSON.parseObject(this.getExt(), DustbinExt.class);
    }
    
    public void setExtObject(DustbinExt ext) {
        this.ext = JSON.toJSONString(ext);
    }
    
    ...
    

    可以看到 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 5.7.8 以下版本,我的建议是, ext 扩展字段不要存储热点数据,只存储非热点数据,这样就可以避免查询操作,降低维护 ext 字段带来的成本和风险,那如何识别新增字段是不是热点数据呢?这个需要结合实际业务需求来判断,也可以询问对业务和技术更有经验的同事,便于读者更快得出结论。

    终极版解决方案

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

    总结

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

    近期热文推荐:

    1.1,000+ 道 Java面试题及答案整理(2021最新版)

    2.终于靠开源项目弄到 IntelliJ IDEA 激活码了,真香!

    3.阿里 Mock 工具正式开源,干掉市面上所有 Mock 工具!

    4.Spring Cloud 2020.0.0 正式发布,全新颠覆性版本!

    5.《Java开发手册(嵩山版)》最新发布,速速下载!

    觉得不错,别忘了随手点赞+转发哦!

  • 相关阅读:
    strcpy 详解
    c/c++中static详解
    c/c++中const详解
    12、Java中的接口
    11、java中的模板方法设计模式
    10、java中的抽象类
    9、java中的final关键字
    8、java继承中的this和super的应用
    7、java实现的两种单例模式
    6、java中的构造代码块
  • 原文地址:https://www.cnblogs.com/javastack/p/14955945.html
Copyright © 2011-2022 走看看