如何不改表結構動態擴充套件欄位?
作者:李瑜寧
來源: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 正式釋出,全新顛覆性版本!
覺得不錯,別忘了隨手點贊+轉發哦!