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

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 痛点软件行业唯一不变的就是变化,比如功能上线之后,客户或 PM 需要对已有的功能增加一些合理的需求,完成这些工作必须通过添加字段解决,或者某些功能的实现需要通过增加字段来降低实现的复杂性等等。

痛点

软件行业唯一不变的就是变化,比如功能上线之后,客户或 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;
    }
    ...
}

image.png

...
/**
 * 扩展字段,详细字段查看 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 字段带来的成本和风险,那如何识别新增字段是不是热点数据呢?这个需要结合实际业务需求来判断,也可以询问对业务和技术更有经验的同事,便于读者更快得出结论。


终极版解决方案

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


总结

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

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
5天前
|
存储 SQL 数据管理
平台设计-固定表结构与可自定义表结构
整个平台的表结构分为两种:固定的和可自定义的。
|
7月前
|
存储 XML SQL
浅谈扩展字段设计
浅谈扩展字段设计
219 0
|
9月前
|
SQL 关系型数据库 MySQL
php开发实战分析(1):mysql操作字段(添加、删除、修改,多数据表中新增多个字段)
php开发实战分析(1):mysql操作字段(添加、删除、修改,多数据表中新增多个字段)
117 0
|
存储 SQL Java
Mysql数据库表字段设计优化(状态列)
初始状态码(java int 32 long 64),int 可以表示31种(除去0000),long可以表示63种(除去0000),当然不可能将0000赋值给初始状态,一般来讲,选择int还是long是根据具体业务需求来决定的。
394 0
Mysql数据库表字段设计优化(状态列)
|
数据库
【TP5】根据数据库字段注释使用同一模板进行增删查(1)
【TP5】根据数据库字段注释使用同一模板进行增删查
138 0
【TP5】根据数据库字段注释使用同一模板进行增删查(1)
|
数据库
【TP5】根据数据库字段注释使用同一模板进行增删查(2)
【TP5】根据数据库字段注释使用同一模板进行增删查
85 0
【TP5】根据数据库字段注释使用同一模板进行增删查(2)
|
关系型数据库 MySQL 数据库
数据库中的表如何设置约束?(精简版)
数据库中的表如何设置约束?(精简版)
354 0
|
数据库
【自然框架】元数据的数据库结构的详细说明和示例(三):项目与数据库字段的关联
【自然框架】PowerDesigner 格式的元数据的表结构 【自然框架】元数据的数据库结构的详细说明和示例(一):项目描述部分 【自然框架】元数据的数据库结构的详细说明和示例(二):数据库描述部分     1、Manage_FunListCol(列表用字段) 字段名...
944 0
|
存储 数据库
【自然框架】PowerDesigner 格式的元数据的表结构
自然框架里的元数据 元数据的职责:  自然框架里的元数据有三个职责:描述数据库(字段、表、视图等),描述项目(功能节点、操作按钮等),项目和数据库的关系(一个列表页面里需要显示哪些字段、哪些查询条件等) 元数据的存储:  有两个存储元数据的地方,一个是数据库,另一个是实体类。
1001 0