不改表结构如何动态扩展字段
筆者的動(dòng)態(tài)字段擴(kuò)展解決方案主要針對(duì) Mysql 5.7.8 以下版本,在 Mysql 5.7.8 已經(jīng)新增?JSON Data Type,同樣適用該方案,而且情況變得更加簡(jiǎn)單。
痛點(diǎn)
軟件行業(yè)唯一不變的就是變化,比如功能上線之后,客戶或 PM 需要對(duì)已有的功能增加一些合理的需求,完成這些工作必須通過(guò)添加字段解決,或者某些功能的實(shí)現(xiàn)需要通過(guò)增加字段來(lái)降低實(shí)現(xiàn)的復(fù)雜性等等。這些問(wèn)題都會(huì)改動(dòng)線上的數(shù)據(jù)庫(kù)表結(jié)構(gòu),一旦改動(dòng)就會(huì)導(dǎo)致鎖表,會(huì)使所有的寫(xiě)入操作一直等待,直到表鎖關(guān)閉,特別是對(duì)于數(shù)據(jù)量大的熱點(diǎn)表,添加一個(gè)字段可能會(huì)因?yàn)殒i表時(shí)間過(guò)長(zhǎng)而導(dǎo)致部分請(qǐng)求超時(shí),這可能會(huì)對(duì)企業(yè)間接造成經(jīng)濟(jì)上的損失。
?
解決方案
增加 json 格式的擴(kuò)展字段。
下面配合一些代碼來(lái)描述這個(gè)解決方案,讀者便于去理解。
mysql 數(shù)據(jù)庫(kù)腳本:
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?卡號(hào)',`state`?INT(1)?NOT?NULL?COMMENT?'垃圾桶狀態(tài):0:已注銷(xiāo);1:未使用;2:待使用;3:已使用(綁定收集點(diǎn));',`user_id`?INT?NOT?NULL?COMMENT?'登記人,負(fù)責(zé)錄入垃圾桶的人',`type`?INT(1)?NOT?NULL?DEFAULT?1?COMMENT?'垃圾桶類型:1:餐廚垃圾桶',`street_code`?INT(11)?DEFAULT?NULL?COMMENT?'所在鎮(zhèn)街?code,根據(jù)狀態(tài),這里的含義可能是領(lǐng)用鎮(zhèn)街、退還鎮(zhèn)街。',`create_time`?DATETIME?NOT?NULL?DEFAULT?now()?COMMENT?'創(chuàng)建時(shí)間',`update_time`?DATETIME?NOT?NULL?DEFAULT?now()?COMMENT?'更新時(shí)間',`ext`?VARCHAR(1000)?NOT?NULL?DEFAULT?'{}'?COMMENT?'擴(kuò)展字段',...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;/***?垃圾桶實(shí)體*?Created?by?Blink?on?6/28/2018?AD.**?@author?Blink*/ @Data public?class?Dustbin?{private?String?id;/***?rfid?卡號(hào)*/@NotNullprivate?String?rfidNo;/***?垃圾桶狀態(tài):0:已注銷(xiāo);1:未使用;2:待使用;3:已使用(綁定收集點(diǎn));*?對(duì)應(yīng)?Dustbin.StateEnum?類*/@NotNullprivate?Integer?state;/***?錄入垃圾桶的人員id*/@NotNullprivate?Long?userId;/***?垃圾桶類型:1:餐廚垃圾桶*?DefaultValue:?1*/@NotNullprivate?Integer?type;/***?所在鎮(zhèn)街?code*?根據(jù)狀態(tài),這里的含義可能是領(lǐng)用鎮(zhèn)街、退還鎮(zhèn)街*/private?Integer?streetCode;/***?創(chuàng)建時(shí)間*?defaultValue?:?now()*/@NotNullprivate?Date?createTime;/***?更新時(shí)間*/@NotNullprivate?Date?updateTime;/***?擴(kuò)展字段,詳細(xì)數(shù)據(jù)查看?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);}/***?垃圾桶擴(kuò)展屬性*?Created?by?Blink?on?6/28/2018?AD.**?@author?Blink*/@Datapublic?static?class?DustbinExt?{/***?所在鎮(zhèn)街*?根據(jù)狀態(tài),這里的含義可能是領(lǐng)用鎮(zhèn)街、退還鎮(zhèn)街、綁定的鎮(zhèn)街*/private?String?street;/***?客戶(收集點(diǎn))id,綁定收集點(diǎn)的時(shí)候需要填入*?根據(jù)目前的需求(2018-06-29),當(dāng)收集點(diǎn)解綁的時(shí)候*?需要保存垃圾桶最新綁定收集點(diǎn)名稱,所以在解綁垃圾桶的時(shí)候不會(huì)把這個(gè)信息刪掉*?只有當(dāng)綁定收集點(diǎn)的時(shí)候才把他覆蓋*/private?Long?customerId;/***?客戶(收集點(diǎn))名稱,綁定收集點(diǎn)的時(shí)候需要填入*?根據(jù)目前的需求(2018-06-29),當(dāng)收集點(diǎn)解綁的時(shí)候*?需要保存垃圾桶最新綁定收集點(diǎn)名稱,所以在解綁垃圾桶的時(shí)候不會(huì)把這個(gè)信息刪掉*?只有當(dāng)綁定收集點(diǎn)的時(shí)候才把他覆蓋*/private?String?customer;/***?損壞部位*?1:桶蓋;2:桶口;3:桶身;4:桶軸;5:桶底;6:桶輪;*?對(duì)應(yīng)?DustbinDamagePartEnum?類*/private?List<Integer>?parts;}... }mysql 腳本可以看到擴(kuò)展字段的信息:
ext?VARCHAR(1000)?NOT?NULL?DEFAULT?'{}'?COMMENT?'擴(kuò)展字段'可以看到這么一段 Java 代碼:
????.../***?擴(kuò)展字段,詳細(xì)字段查看?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?字段就是用來(lái)存儲(chǔ) json 格式的數(shù)據(jù),它可以動(dòng)態(tài)地增加任何字段,甚至是對(duì)象,不需要通過(guò)?DDL(Data Definition Language)?去創(chuàng)建字段,非常適合用來(lái)解決上面提到的問(wèn)題。
Java 代碼在這里起到輔助性作用,通過(guò)定義一個(gè)內(nèi)部類來(lái)管理擴(kuò)展字段的屬性,方便我們了解和管理擴(kuò)展字段,提高代碼的可讀性和可維護(hù)性,java 這種方式也是筆者總結(jié)出來(lái)的較為優(yōu)雅的做法(個(gè)人觀點(diǎn))。
?
局限性
有經(jīng)驗(yàn)的讀者可能會(huì)提出,ext?字段在 Mysql 5.7.8 以下版本無(wú)法對(duì)擴(kuò)展字段中的某一個(gè)或一部分字段建立索引,因?yàn)?Mysql 5.7.8 版本以下不支持(Mysql 5.7.8 支持為 Json Data Type 建立索引)。
沒(méi)錯(cuò),這是這個(gè)解決方案的一個(gè)局限性,在 Mysql 5.7.8 以下版本,我的建議是,?ext 擴(kuò)展字段不要存儲(chǔ)熱點(diǎn)數(shù)據(jù),只存儲(chǔ)非熱點(diǎn)數(shù)據(jù),這樣就可以避免查詢操作,降低維護(hù)?ext?字段帶來(lái)的成本和風(fēng)險(xiǎn),那如何識(shí)別新增字段是不是熱點(diǎn)數(shù)據(jù)呢?這個(gè)需要結(jié)合實(shí)際業(yè)務(wù)需求來(lái)判斷,也可以詢問(wèn)對(duì)業(yè)務(wù)和技術(shù)更有經(jīng)驗(yàn)的同事,便于讀者更快得出結(jié)論。
?
終極版解決方案
在一些極端的情況下,變化可能來(lái)得太快,而我們要的是減少變化帶來(lái)的成本和風(fēng)險(xiǎn),所以在表設(shè)計(jì)之初可以根據(jù)自身經(jīng)驗(yàn),或者找更有經(jīng)驗(yàn)的人尋求幫助,預(yù)估一下需要預(yù)留多少個(gè)備用字段,再配合擴(kuò)展字段,基本上可以把改變(添加字段)表結(jié)構(gòu)的次數(shù)降至一個(gè)非常少的次數(shù)。
?
總結(jié)
在特殊情況下,通過(guò)擴(kuò)展字段 + 預(yù)留字段基本上可以做到動(dòng)態(tài)擴(kuò)展字段,又不會(huì)影響為熱點(diǎn)數(shù)據(jù)建立索引的情況,這樣我們得到了一個(gè)非常靈活的表結(jié)構(gòu),便于我們應(yīng)對(duì)未來(lái)的變化,**但是請(qǐng)注意,要維護(hù)好我們的實(shí)體,包括里面的每一個(gè)字段,敬畏每一行代碼。
總結(jié)
以上是生活随笔為你收集整理的不改表结构如何动态扩展字段的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 大话程序猿眼里最全的高并发,快收藏!
- 下一篇: 八种 WebSocket 框架的性能比较