閉眼建表所需的 18 條軍規
最近這段時間同事們都休高溫假,苦了我們這邊今年的新員工了,經常讀本號文章的小伙伴應該也看出來了吧,已經接近一個月沒有原創了,沒辦法,一個人干n個人的活,工作趕進度,干貨文章也就擱置了,只能慢慢的補上來。
正好最近公司新需求來了,又是假期沒人的時候,所以我這塊磚就被搬出來了。
新需求也就意味著新設計,原來的表設計也不能滿足新需求,需要設計表了。
省略復雜的表設計溝通階段,本文只記錄表在設計過程中需要注意的18個規范。
在建表的過程中如果不注意本文所述的18條小細節,等待后面開發,甚至是系統上線,再進行表的修改那代價你就品把。
所以本文就拿我多年工作中踩過的坑,實際的使用經驗來進行分享,非常的有借鑒意義,希望能對你有所幫助,少走彎路少踩坑,省下的時間自由摸魚。
1.命名
(1) 字段命名
中國人的命名習慣就是中式英語,所以起名時每個人就有每個人的特色,在進行表設計時,盡可能的進行統一,要么全部中文拼音,要么全部英文,切記不要混用,那樣真的太難受了。
給表、字段名給個好名字,真的太重要了,最好做到見名知意。
推薦一個起名網站,再不濟就去 ChatGPT 起名。
https://unbug.github.io/codelf/
就拿用戶名來說舉個例子:
正例:
用戶名:username
反例:
用戶名:yong_hu_name,name等
需要注意的是,見名知意不錯,切記名字也不可太長。
說完了起名,再說一下大小寫。
(2) 大小寫
在國產數據庫中,有的會默認搞成全大寫,小寫的有的反而不兼容會有 Bug 產生,而站在視覺感官上來說,小寫加下劃線的形式更加易讀,更加直觀,所以在命名時,還是那句話,統一規則。
要么全部大寫加下劃線,要么全部小寫加下劃線,禁止大小寫混用。
推薦全部小寫加下劃線的形式。
大小寫混用的拉出去砍了。
正例:
產品名稱:product_name,PRODUCT_NAME
反例:
產品名稱:product_NAME,PRODUCT_name
(3) 分隔符
在給字段起名時,很多場景下單個單詞無法滿足我們的命名要求,那么多個單詞怎么連接呢?
推薦使用_下劃線進行連接。
還有駝峰形式或者不使用連接符,這些都是禁止的,有的框架在使用駝峰時會遇到轉換問題。
使用連接符可讀性太差,這誰家好人能一眼讀出來一大長串啊是不是。
正例:
產品名稱:product_name,PRODUCT_NAME
反例:
產品名稱:productname,productName,product@name
(4) 關鍵字
上面說過起名要見名知意,但是也要避免與數據庫中的關鍵字沖突,比如工作中經常用過的status。
比如涉及到關鍵字的,可以假如業務來進行區分。
創建時間:create_time
更新時間:update_time
刪除狀態:delete_status,deleted
(5) 索引名
索引的命名由索引的類型來分類,因為索引有很多種,主鍵、唯一、普通、聯合、空間等,
通過索引名稱可以一眼看出來是普通索引還是唯一索引,或者聯合索引那么這個索引的名稱就是規范的。
比如聯合索引按照字段順序進行命名,唯一索引加入前綴uniq。
(6) 表名
字段相關名稱的說完了,還有表名在提一下,表的命名中,除了體現當前表含義外最好加入業務前綴。
比如訂單相關的表用order_前綴。
2.字段類型
對于字段的類型,可選擇的太多了,時間類型我們可以使用date,datetime,timestamp,也可以用 bigint 等等。
字符類型的有varchar、char、text等,數字有int,bigint,tinyint,smallint等。
其實這么多都挑花眼了,不知道用哪個,還不如全用varchar,是不是你就是這樣做的?
如何選擇一個合適的字段類型,就變成了我們不得不考慮的問題了。
比如狀態值,10以內的數字,每個數字1個字節就夠,使用tinyint即可,如果選了bigint,反而會白白浪費空間。
所以我們可以參考以下原則:
- 滿足業務需求的情況下,盡可能選擇占用存儲空間小的字段類型。
- 字段長度固定的可以選擇char,不固定的可以選擇varchar。
- 是否這種true或者false的字段,可以使用bit類型。
- 枚舉字段可以tinyint類型。
- 主鍵使用bigint類型。
- 金額字段可以使用decimal或者換算單位存bigint。
- 時間字段使用datetime或者timestamp或者轉換時間戳存bigint。
3.字段長度
上面字段類型的選擇中提到了長度,接下來我們就重點說一下長度的選擇。
varchar(255) 中 255 代表的是字符長度。而在 MySQL中,除了 varchar 和 char 代表的是字符長度之外,其他的類型都是字節長度。
bigint 的實際長度是 8 個字節,bigint(4) 代表當不滿 4 個字節的時候,前面填充0(前提是開啟了自動填充)。
當超過4個字節時按照實際情況展示。
比如現在的數據是 12345,展示的時候也是展示12345。
但是需要注意的是有的 MySQL 客戶端只會展示 4 個字節,比如展示成 1234,所以 bigint(4)中的 4 表示的是顯示長度,實際占用還是8個字節。
4.字段個數
大家在看數據庫表優化時應該經常聽到的就是減少表的字段個數,防止寬表的發生。
所以我們在建表時最好控制一下字段數量,我上家單位涉及的業務類型的表,那真的是字段巨多,對于這種場景,我們可以大表拆分小表,每個表擁有一個共同的唯一ID做主鍵進行關聯。
建議每個表的字段數量控制在20個,如果字段太多,表中數據存儲量大了之后嚴重影響查詢效率。
5.主鍵
不知道你們有沒有遇到過,我是遇到過表連個主鍵都沒有,全是普通的列,索引更不用說當然也沒有了。
之所以每個表都需要有個主鍵是因為,主鍵索引相比其他的索引在查詢時可以避免回表,提升查詢效率。而且主鍵索引也是唯一索引,可以作為業務的去重。
在單體數據庫中使用默認的自增ID做主鍵即可,效率還是很高的。在分布式環境中,最好還是使用遞增的分布式ID算法,保證全局唯一。
需要注意的是,主鍵建議保存與業務無關的值,方便后面擴展。
分布式ID生成算法可以看下之前的這篇文章:全網最全的分布式ID分析
6.外鍵
說完主鍵說一下外鍵,這個避免使用吧。
說實話,不好用,外鍵本來的作用是保證數據的一致,關聯表少的時候還沒啥,等關聯表數量上來之后,在進行刪除等操作時,性能是非常差的。
除了外鍵還有就是觸發器以及存儲過程,每次一見到開源框架中有這些就頭疼。
7.索引
表的主鍵索引是必須的,對于其他的索引,根據自己的業務場景進行添加即可,但是一個表的索引數量盡量也不要太多,建議單表索引數量不要超過5個。
創建索引時盡可能的考慮索引覆蓋、最左前綴、索引下推等優化方案。
需要注意的是對于重復性較高的字段也不建議創建索引,因為這樣沒意義。
8.唯一索引
這里為什么會單獨把唯一索引揪出來呢,還是因為有坑啊,大家在使用唯一索引時,如果是單個字段倒還好,如果是多個字段的,那你一定要注意了,如果有 null 值的出現,唯一性約束可能會失效哦,對于唯一索引的坑下一篇文章單獨拿出來講講。
9.NOT NULL
建議大家在設計表時,能確保不會出現 NULL值的列設置為 NOT NULL ,這是因為當存儲引擎是 Innodb 時,對于NULL值會占用更多的空間,且查詢時 NULL 值也會造成索引失效,查詢條件只能用時IS NULL或者IS NOT NULL 進行判斷。
因此建議能定義為NOT NULL,就定義為NOT NULL。
定義為NOT NULL也有好處,當 INSERT 時如果漏掉了某個字段的值,直接報錯提醒出來,多么明顯的報錯。
還有一種情況就是在現有的表中增加字段,此時歷史數據中對于新增加的字段是沒有值的,因為設置NOT NULL 的字段盡量也都賦一個默認值。
10.存儲引擎
這個應該沒什么說的了,大部分都是使用的 Innodb,如果不是,去看看改一下吧。如果你的業務場景適合其他引擎或者你們有自己開發的引擎,當我沒說。
如果你不知道為啥使用 Innodb,那么現在你知道了,因為 Innodb 支持事務,且性能越來越優秀。
11.時間字段
下面 就是對數據庫中的個別容易產生 bug 的字段類型進行分析。
首先就是時間字段,畢竟時間類型太多了,我們存儲時間可以使用 date、datetime、timestamp、varchar、bigint等。
varchar 保存有點就是易讀,直接返回給前端,省去了轉換的過程。
date 只能保存日期,沒有時間,看需求。
datetime 與timestamp 更適合我們保存時間,但是他們也有區別。
(1) datetime
- datetime 存儲的時間范圍更廣,在MySQL中,可以表示從 1000-01-01 到 9999-12-31 之間的日期和時間。
- datetime 不涉及時區轉換。
- datetime 不支持自動更新。
(2) timestamp
- timestamp 存儲范圍較窄,在MySQL 中,可以表示從 1970-01-01 00:00:00 UTC 到 2038-01-19 03:14:07 UTC 的日期時間。
- timestamp 通常以 UTC 存儲,所以需要進行時區轉換, 比較適合存儲跨時區的數據。
- timestamp 在 MySQL 中還可以設置更新時間字段,設置為自動更新。
需要注意的是,在給時間設置默認值時,不要設置0000-00-00 00:00:00 ,防止查詢時時間轉換報錯。
出了上面幾種,還可以使用 bigint 存儲時間戳的形式,除了可讀性以及需要轉換外,好像也沒啥大問題,你們有用這種方式存儲時間的嗎。
12.金額字段
金額字段想到的就是浮點類型 float,double,decimal等。
而 float、double 會丟失精度就算了還是別用了,因此還是推薦你用 decimal ,但是需要注意 decimal 使用時的幾個坑,還不了解沒關系,鏈接我放下面了。
如果你還是不想用 decimal ,那么再推薦你一種,轉換為分或者更小的貨幣單位,使用 bigint 存儲。
13.json字段
這個字段一直是我不想用的,因為兼容不好。如果后期需要切換數據庫,假如正好你切換的數據庫不支持json類型,那么恭喜你,改代碼吧。
這段時間正好新需求,試了一下這個 json 字段,感覺用起來還是不錯的,前提是兼容 json 格式。
不好的地方就是對數據的處理查詢上還是沒有那么方便。
一句話,能不用還是不用吧,建議 json 類型直接存儲 varchar,然后代碼中轉換一下更好,畢竟不用考慮兼容問題啊。
14.大字段
如果你用了json,那么不可避免的會有大字段的可能,大字段的定義就是占用存儲空間多的字段。
對于大文本如果直接定義為 text 類型,可能會浪費存儲空間。如果業務可以對該字段進行一個最大長度的限制,那么我們可以使用 varchar 類型進行存儲,效率更高。
還有一個類型就是 blob ,直接存儲文件內容。如果你們也這樣做,建議還是換了吧,這個設計有點不合理了,
上個存儲保存個文件地址多好。
15.冗余字段
在設計表的時候,為了查詢的性能考慮,可能會冗余一些信息字段,比如說某個表中需要記錄用戶的 userId,當我們需要用戶名稱的時候,還需要通過 userId 進行關聯查詢獲取 username ,那么我們就可以冗余 username 到我們的表中,提升我們的查詢效率。
相當于空間換時間的概念,犧牲這一點空間,減少的卻是 join 查詢的時間,對查詢性能的提升很有幫助。
不能光說好的地方,也有壞的地方,有存儲的地方就得有維護,容易造成數據的不一致。
所以在使用中也是根據自己的業務綜合評估,選擇一個更適合自己業務的方法。
16.注釋
表注釋以及字段注釋,與代碼開發中的代碼注釋沒差別,都得寫清楚啊,假如是個狀態值1、2、3、4、5的,不寫注釋時間長了你知道是什么意思嗎?
需要注意一點哈,寫的注釋注意與代碼中的保持同步,別到了最后一個字段好多個含義,最后弄的自己都要分不清哪個是什么意思那不悲催了。
17.字符集
說了那么多表里面的東西,在說一下底層最基礎的編碼。MySQL 中支持的編碼類型還是很多的,不過這邊建議使用utf8mb4,因為 utf8 是沒法存儲 emoji 表情的,所以被替代也是個趨勢吧,使用 ut8mb4 能省去很多的麻煩。
常用的 gbk、utf8、utf8mb4區別如下:
- gbk 包含了 GB2312 標準中的所有字符,不支持 Unicode 標準,所以只能在中國使用,在處理多語言時能力有限。
- utf8是一種可變長度的 Unicode 編碼方法,兼容性也好,是一種廣泛使用的標準,支持多種語言。缺點就是不支持emoji 表情。
- utf8mb4 是 utf8 的一個擴展,也是 MySQL 中的推薦字符集,尤其是支持表情符號和特殊字符。
18.排序規則
上面說了字符集,排序規則與字符集也是息息相關的。在 mysql 中,如果你的字符集設置的是 utf8mb4,那么你的排序規則也是 utf8mb4 開頭的,常用的就是utf8mb4_general_ci,utf8mb4_bin。
- utf8mb4_general_ci 的排序規則對大小寫是不敏感的,簡單地說就是a與A相等,他會認為這倆是相同的字符。
- utf8mb4_bin是區分大小寫的,a與A會被認為是不同的字符。
所以排序規則還是要根據我們的業務場景進行選擇,比如用戶的登錄密碼。
總結
本文總結了工作多年建表的一點心得,希望對你有所幫助,下面我們一起回顧一下:
- 表的字符集、排序規則統一,根據自己的業務需要選擇合適的編碼。
- 在起名上見名知意,不管是表名還是字段名、索引名,統一起名規則。
- 在字段上,控制表字段個數,防止寬表的產生,字段類型上滿足業務的前提下選擇占用存儲空間少的字段,避免大字段的產生,可以使用冗余字段加速查詢,對于不了解的類型少用或不用。
- 關于索引方面,每個表必須有主鍵索引,其次唯一索引使用時注意避坑。
- 最后使用支持事務的 Innodb 引擎。