后端程序員必備:15個(gè)MySQL表設(shè)計(jì)的經(jīng)驗(yàn)準(zhǔn)則
前言
大家好,我是田螺。
昨天星球粉絲,咨詢了一個(gè)并發(fā)的問(wèn)題~ 我提供了一個(gè)樂(lè)觀鎖兜底的方案,然后發(fā)現(xiàn)他們的表,都沒(méi)有加version字段的,我想到,這不是表設(shè)計(jì)通用字段嘛。因此,本文跟大家聊聊,設(shè)計(jì)表的時(shí)候,有哪些經(jīng)驗(yàn)準(zhǔn)則。
1. 設(shè)計(jì)表時(shí),盡量都有這幾個(gè)通用字段
表必備一般來(lái)說(shuō),或具備這幾個(gè)字段:
- id:主鍵,一個(gè)表必須得有主鍵,必須
- create_time:創(chuàng)建時(shí)間,必須
- modifed_time: 修改時(shí)間,必須,更新記錄時(shí),就更新它。
- version : 數(shù)據(jù)記錄的版本號(hào),一般用于樂(lè)觀鎖,非必須
- modifier :修改人,非必須
- creator :創(chuàng)建人,非必須
2. 每個(gè)字段都要有注釋,尤其涉及枚舉這些時(shí)
我們?cè)谠O(shè)計(jì)表的時(shí)候,每個(gè)字段,都要寫(xiě)上注釋哈,尤其涉及到一個(gè)枚舉字段的時(shí)候,更要把每個(gè)枚舉值寫(xiě)出來(lái),后面如果有變更,也要維護(hù)到這里來(lái)~
反例:
CREATE TABLE order_tab (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT UNIQUE,
user_id BIGINT NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'PENDING',
payment_status VARCHAR(20) DEFAULT 'not_paid',
version INT DEFAULT 0,
created_time DATETIME,
updated_time DATETIME,
creator VARCHAR(255),
modifier VARCHAR(255)
);
正例:
CREATE TABLE order_tab (
id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '訂單項(xiàng)的唯一標(biāo)識(shí)符,自增主鍵',
order_id BIGINT UNIQUE COMMENT '訂單的唯一標(biāo)識(shí)符,在整個(gè)系統(tǒng)中唯一',
user_id BIGINT NOT NULL COMMENT '用戶的唯一標(biāo)識(shí)符,關(guān)聯(lián)到用戶表',
total_amount DECIMAL(10, 2) NOT NULL COMMENT '訂單的總金額,精確到小數(shù)點(diǎn)后兩位',
status VARCHAR(20) NOT NULL DEFAULT 'PENDING' COMMENT '訂單的狀態(tài),例如:PENDING(待處理)、COMPLETED(已完成)等',
payment_status VARCHAR(20) DEFAULT 'not_paid' COMMENT '訂單的支付狀態(tài),如:not_paid(未支付)、paid(已支付)等',
version INT DEFAULT 0 COMMENT '樂(lè)觀鎖版本號(hào),用于并發(fā)控制',
created_time DATETIME COMMENT '訂單的創(chuàng)建時(shí)間',
updated_time DATETIME COMMENT '訂單的最后一次更新時(shí)間',
creator VARCHAR(255) COMMENT '訂單的創(chuàng)建者,通常記錄創(chuàng)建訂單的用戶或系統(tǒng)的用戶名',
modifier VARCHAR(255) COMMENT '訂單的修改者,通常記錄最后修改訂單的用戶或系統(tǒng)的用戶名'
);
3. 命名規(guī)范
數(shù)據(jù)庫(kù)表名、字段名、索引名等都需要命名規(guī)范,可讀性高(一般要求用英文),讓別人一看命名,就知道這個(gè)字段表示什么意思。
比如一個(gè)表的賬號(hào)字段,反例如下:
acc_no,1_acc_no,zhanghao
正例:
account_no,account_number
- 表名、字段名必須使用小寫(xiě)字母或者數(shù)字,禁止使用數(shù)字開(kāi)頭,禁止使用拼音,并且一般不使用英文縮寫(xiě)。
- 主鍵索引名為pk_字段名;唯一索引名為uk_字段名;普通索引名則為idx_字段名。
4. 選擇合適的字段類型
設(shè)計(jì)表時(shí),我們需要選擇合適的字段類型,比如:
- 盡可能選擇存儲(chǔ)空間小的字段類型,就好像數(shù)字類型的,從tinyint、smallint、int、bigint從左往右開(kāi)始選擇
- 小數(shù)類型如金額,則選擇 decimal,禁止使用 float 和 double。
- 如果存儲(chǔ)的字符串長(zhǎng)度幾乎相等,使用 char 定長(zhǎng)字符串類型。
- varchar是可變長(zhǎng)字符串,不預(yù)先分配存儲(chǔ)空間,長(zhǎng)度不要超過(guò)5000。
- 如果存儲(chǔ)的值太大,建議字段類型修改為text,同時(shí)抽出單獨(dú)一張表,用主鍵與之對(duì)應(yīng)。
- 同一表中,所有varchar字段的長(zhǎng)度加起來(lái),不能大于65535. 如果有這樣的需求,請(qǐng)使用TEXT/LONGTEXT 類型。
5. 主鍵設(shè)計(jì)要合理
主鍵設(shè)計(jì)的話,最好不要與業(yè)務(wù)邏輯有所關(guān)聯(lián)。有些業(yè)務(wù)上的字段,比如身份證,雖然是唯一的,一些開(kāi)發(fā)者喜歡用它來(lái)做主鍵,但是不是很建議哈。主鍵最好是毫無(wú)意義的一串獨(dú)立不重復(fù)的數(shù)字,比如UUID,又或者Auto_increment自增的主鍵,或者是雪花算法生成的主鍵等等;
6.選擇合適的字段長(zhǎng)度
先問(wèn)大家一個(gè)問(wèn)題,大家知道數(shù)據(jù)庫(kù)字段長(zhǎng)度表示字符長(zhǎng)度還是字節(jié)長(zhǎng)度嘛?
其實(shí)在mysql中,varchar和char類型表示字符長(zhǎng)度,而其他類型表示的長(zhǎng)度都表示字節(jié)長(zhǎng)度。比如char(10)表示字符長(zhǎng)度是10,而bigint(4)表示顯示長(zhǎng)度是4個(gè)字節(jié),但是因?yàn)閎igint實(shí)際長(zhǎng)度是8個(gè)字節(jié),所以bigint(4)的實(shí)際長(zhǎng)度就是8個(gè)字節(jié)。
我們?cè)谠O(shè)計(jì)表的時(shí)候,需要充分考慮一個(gè)字段的長(zhǎng)度,比如一個(gè)用戶名字段(它的長(zhǎng)度5~20個(gè)字符),你覺(jué)得應(yīng)該設(shè)置多長(zhǎng)呢?可以考慮設(shè)置為 username varchar(32)。字段長(zhǎng)度一般設(shè)置為2的冪哈(也就是2的n次方)。
7. 優(yōu)先考慮邏輯刪除,而不是物理刪除
什么是物理刪除?什么是邏輯刪除?
- 物理刪除:把數(shù)據(jù)從硬盤中刪除,可釋放存儲(chǔ)空間
- 邏輯刪除:給數(shù)據(jù)添加一個(gè)字段,比如is_deleted,以標(biāo)記該數(shù)據(jù)已經(jīng)邏輯刪除。
物理刪除就是執(zhí)行delete語(yǔ)句,如刪除account_no =‘666’的賬戶信息SQL如下:
delete from account_info_tab whereaccount_no ='666';
邏輯刪除呢,就是這樣:
update account_info_tab set is_deleted = 1 where account_no ='666';
為什么推薦用邏輯刪除,不推薦物理刪除呢?
- 為什么不推薦使用物理刪除,因?yàn)榛謴?fù)數(shù)據(jù)很困難
- 物理刪除會(huì)使自增主鍵不再連續(xù)
- 核心業(yè)務(wù)表 的數(shù)據(jù)不建議做物理刪除,只適合做狀態(tài)變更。
8. 一張表的字段不宜過(guò)多
我們建表的時(shí)候,要牢記,一張表的字段不宜過(guò)多哈,一般盡量不要超過(guò)20個(gè)字段哈。筆者記得上個(gè)公司,有伙伴設(shè)計(jì)開(kāi)戶表,加了五十多個(gè)字段。。。
如果一張表的字段過(guò)多,表中保存的數(shù)據(jù)可能就會(huì)很大,查詢效率就會(huì)很低。因此,一張表不要設(shè)計(jì)太多字段哈,如果業(yè)務(wù)需求,實(shí)在需要很多字段,可以把一張大的表,拆成多張小的表,它們的主鍵相同即可。
當(dāng)表的字段數(shù)非常多時(shí),可以將表分成兩張表,一張作為條件查詢表,一張作為詳細(xì)內(nèi)容表 (主要是為了性能考慮)。
9. 盡可能使用not null定義字段
如果沒(méi)有特殊的理由, 一般都建議將字段定義為 NOT NULL 。
為什么呢?
- 首先, NOT NULL 可以防止出現(xiàn)空指針問(wèn)題。
- 其次,NULL值存儲(chǔ)也需要額外的空間的,它也會(huì)導(dǎo)致比較運(yùn)算更為復(fù)雜,使優(yōu)化器難以優(yōu)化SQL。
- NULL值有可能會(huì)導(dǎo)致索引失效
- 如果將字段默認(rèn)設(shè)置成一個(gè)空字符串或常量值并沒(méi)有什么不同,且都不會(huì)影響到應(yīng)用邏輯, 那就可以將這個(gè)字段設(shè)置為NOT NULL。
10. 設(shè)計(jì)表時(shí),評(píng)估哪些字段需要加索引
首先,評(píng)估你的表數(shù)據(jù)量。如果你的表數(shù)據(jù)量只有一百幾十行,就沒(méi)有必要加索引。否則設(shè)計(jì)表的時(shí)候,如果有查詢條件的字段,一般就需要建立索引。但是索引也不能濫用:
- 索引也不要建得太多,一般單表索引個(gè)數(shù)不要超過(guò)5個(gè)。因?yàn)閯?chuàng)建過(guò)多的索引,會(huì)降低寫(xiě)得速度。
- 區(qū)分度不高的字段,不能加索引,如性別等
- 索引創(chuàng)建完后,還是要注意避免索引失效的情況,如使用mysql的內(nèi)置函數(shù),會(huì)導(dǎo)致索引失效的
- 索引過(guò)多的話,可以通過(guò)聯(lián)合索引的話方式來(lái)優(yōu)化。然后的話,索引還有一些規(guī)則,如覆蓋索引,最左匹配原則等等。。
假設(shè)你新建一張用戶表,如下:
CREATE TABLE user_info_tab (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`age` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`create_time` datetime NOT NULL,
`modifed_time` datetime NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
對(duì)于這張表,很可能會(huì)有根據(jù)user_id或者name查詢用戶信息,并且,user_id是唯一的。因此,你是可以給user_id加上唯一索引,name加上普通索引。
CREATE TABLE user_info_tab (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`age` int(11) DEFAULT NULL,
`name` varchar(255) NOT NULL,
`create_time` datetime NOT NULL,
`modifed_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE,
UNIQUE KEY un_user_id (user_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
11. 避免使用MySQL保留字
如果庫(kù)名、表名、字段名等屬性含有保留字時(shí),SQL語(yǔ)句必須用反引號(hào)來(lái)引用屬性名稱,這將使得SQL語(yǔ)句書(shū)寫(xiě)、SHELL腳本中變量的轉(zhuǎn)義等變得非常復(fù)雜。
因此,我們一般避免使用MySQL保留字,如select、interval、desc等等
12. 一般都選擇INNODB存儲(chǔ)引擎
建表是需要選擇存儲(chǔ)引擎的,我們一般都選擇INNODB存儲(chǔ)引擎,除非讀寫(xiě)比率小于1%, 才考慮使用MyISAM 。
有些小伙伴可能會(huì)有疑惑,不是還有MEMORY等其他存儲(chǔ)引擎嗎?什么時(shí)候使用它呢?其實(shí)其他存儲(chǔ)引擎一般除了都建議在DBA的指導(dǎo)下使用。
我們來(lái)復(fù)習(xí)一下這MySQL這三種存儲(chǔ)引擎的對(duì)比區(qū)別吧:
13. 選擇合適統(tǒng)一的字符集
數(shù)據(jù)庫(kù)庫(kù)、表、開(kāi)發(fā)程序等都需要統(tǒng)一字符集,通常中英文環(huán)境用utf8。
MySQL支持的字符集有utf8、utf8mb4、GBK、latin1等。
- utf8:支持中英文混合場(chǎng)景,國(guó)際通過(guò),3個(gè)字節(jié)長(zhǎng)度
- utf8mb4: 完全兼容utf8,4個(gè)字節(jié)長(zhǎng)度,一般存儲(chǔ)emoji表情需要用到它。
- GBK :支持中文,但是不支持國(guó)際通用字符集,2個(gè)字節(jié)長(zhǎng)度
- latin1:MySQL默認(rèn)字符集,1個(gè)字節(jié)長(zhǎng)度
14. 時(shí)間的類型選擇
我們?cè)O(shè)計(jì)表的時(shí)候,一般都需要加通用時(shí)間的字段,如create_time、modified_time等等。那對(duì)于時(shí)間的類型,我們?cè)撊绾芜x擇呢?
對(duì)于MySQL來(lái)說(shuō),主要有date、datetime、time、timestamp 和 year。
- date :表示的日期值, 格式y(tǒng)yyy-mm-dd,范圍1000-01-01 到 9999-12-31,3字節(jié)
- time :表示的時(shí)間值,格式 hh:mm:ss,范圍-838:59:59 到 838:59:59,3字節(jié)
- datetime:表示的日期時(shí)間值,格式y(tǒng)yyy-mm-dd hh:mm:ss,范圍1000-01-01 00:00:00到9999-12-31 23:59:59```,8字節(jié),跟時(shí)區(qū)無(wú)關(guān)
- timestamp:表示的時(shí)間戳值,格式為yyyymmddhhmmss,范圍1970-01-01 00:00:01到2038-01-19 03:14:07,4字節(jié),跟時(shí)區(qū)有關(guān)
- year:年份值,格式為yyyy。范圍1901到2155,1字節(jié) 推薦優(yōu)先使用datetime類型來(lái)保存日期和時(shí)間,因?yàn)榇鎯?chǔ)范圍更大,且跟時(shí)區(qū)無(wú)關(guān)。
15. 安全性考慮
- 數(shù)據(jù)加密:敏感信息,如用戶密碼,應(yīng)進(jìn)行加密存儲(chǔ)。如果是手機(jī)號(hào)、郵箱這些,則建議脫敏