天天看點

如何不改表結構動态擴充字段?

痛點

軟體行業唯一不變的就是變化,比如功能上線之後,客戶或 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;
    }

    ...
}      
如何不改表結構動态擴充字段?
...

/**
 * 擴充字段,詳細字段檢視 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 字段帶來的成本和風險,那如何識别新增字段是不是熱點資料呢?這個需要結合實際業務需求來判斷,也可以詢問對業務和技術更有經驗的同僚,便于讀者更快得出結論。

終極版解決方案

在一些極端的情況下,變化可能來得太快,而我們要的是減少變化帶來的成本和風險,是以在表設計之初可以根據自身經驗,或者找更有經驗的人尋求幫助,預估一下需要預留多少個備用字段,再配合擴充字段,基本上可以把改變(添加字段)表結構的次數降至一個非常少的次數。

總結

在特殊情況下,通過擴充字段 + 預留字段基本上可以做到動态擴充字段,又不會影響為熱點資料建立索引的情況,這樣我們得到了一個非常靈活的表結構,便于我們應對未來的變化,但是請注意,要維護好我們的實體,包括裡面的每一個字段,敬畏每一行代碼。