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

点击上方“芋道源码”,选择“置顶公众号”

技术文章第一时间送达!

源码精品专栏

 
  • 精尽 Dubbo 原理与源码 69 篇

  • 精尽 Netty 原理与源码 61 篇

  • 中文详细注释的开源项目

  • Java 并发源码合集

  • RocketMQ 源码合集

  • Sharding-JDBC 源码解析合集

  • Spring MVC 和 Security 源码合集

  • MyCAT 源码解析合集

来源:

  • 痛点

  • 解决方案

  • 局限性

  • 终极版解决方案

  • 总结


笔者的动态字段扩展解决方案主要针对 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(45NOT NULL COMMENT '主键自增id',
  `rfid_no` VARCHAR(20NOT NULL COMMENT 'rfid 卡号',
  `state` INT(1NOT NULL COMMENT '垃圾桶状态:0:已注销;1:未使用;2:待使用;3:已使用(绑定收集点);',
  `user_id` INT NOT NULL COMMENT '登记人,负责录入垃圾桶的人',
  `type` INT(1NOT NULL DEFAULT 1 COMMENT '垃圾桶类型:1:餐厨垃圾桶',
  `street_code` INT(11DEFAULT NULL COMMENT '所在镇街 code,根据状态,这里的含义可能是领用镇街、退还镇街。',
  `create_time` DATETIME NOT NULL DEFAULT now() COMMENT '创建时间',
  `update_time` DATETIME NOT NULL DEFAULT now() COMMENT '更新时间',
  `ext` VARCHAR(1000NOT 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 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 字段带来的成本和风险,那如何识别新增字段是不是热点数据呢?这个需要结合实际业务需求来判断,也可以询问对业务和技术更有经验的同事,便于读者更快得出结论。

终极版解决方案

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

总结

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




欢迎加入我的知识星球,一起探讨架构,交流源码。加入方式,长按下方二维码噢

640

已在知识星球更新源码解析如下:

  • 《精尽 Dubbo 源码解析系列》69 篇。

  • 《精尽 Netty 源码解析系列》61 篇。

  • 《精尽 Spring 源码解析系列》35 篇。

  • 《精尽 MyBatis 源码解析系列》34 篇。

  • 《数据库实体设计》17 篇。

  • 正在准备更新《精尽 Spring MVC 源码解析系列》


目前在知识星球更新了《Dubbo 源码解析》目录如下:

01. 调试环境搭建
02. 项目结构一览
03. 配置 Configuration
04. 核心流程一览

05. 拓展机制 SPI

06. 线程池

07. 服务暴露 Export

08. 服务引用 Refer

09. 注册中心 Registry

10. 动态编译 Compile

11. 动态代理 Proxy

12. 服务调用 Invoke

13. 调用特性 

14. 过滤器 Filter

15. NIO 服务器

16. P2P 服务器

17. HTTP 服务器

18. 序列化 Serialization

19. 集群容错 Cluster

20. 优雅停机

21. 日志适配

22. 状态检查

23. 监控中心 Monitor

24. 管理中心 Admin

25. 运维命令 QOS

26. 链路追踪 Tracing

... 一共 69+ 篇

目前在知识星球更新了《Netty 源码解析》目录如下:

01. 调试环境搭建
02. NIO 基础
03. Netty 简介
04. 启动 Bootstrap

05. 事件轮询 EventLoop

06. 通道管道 ChannelPipeline

07. 通道 Channel

08. 字节缓冲区 ByteBuf

09. 通道处理器 ChannelHandler

10. 编解码 Codec

11. 工具类 Util

... 一共 61+ 篇


目前在知识星球更新了《数据库实体设计》目录如下:


01. 商品模块
02. 交易模块
03. 营销模块
04. 公用模块

... 一共 17+ 篇


目前在知识星球更新了《Spring 源码解析》目录如下:


01. 调试环境搭建
02. IoC Resource 定位
03. IoC BeanDefinition 载入

04. IoC BeanDefinition 注册

05. IoC Bean 获取

06. IoC Bean 生命周期

... 一共 35+ 篇


目前在知识星球更新了《MyBatis 源码解析》目录如下:


01. 调试环境搭建
02. 项目结构一览
03. MyBatis 面试题合集

04. MyBatis 学习资料合集

05. MyBatis 初始化

06. SQL 初始化

07. SQL 执行

08. 插件体系

09. Spring 集成

... 一共 34+ 篇


源码不易↓↓↓

点赞支持老艿艿↓↓

你可能感兴趣的:(不改表结构如何动态扩展字段)