一文詳解MySQL數(shù)據(jù)表設(shè)計的五大黃金原則
新功能要上線,產(chǎn)品經(jīng)理說要加個字段。你一看表結(jié)構(gòu),倒吸一口涼氣——這表已經(jīng)30多個字段了,而且好幾個字段還是用逗號分隔存一堆值。改還是不改?這是個問題。
類似的問題還有很多,比如:
- 需求變更時,發(fā)現(xiàn)表結(jié)構(gòu)難以擴(kuò)展
- 數(shù)據(jù)出現(xiàn)不一致,排查了半天才發(fā)現(xiàn)是設(shè)計缺陷
- 同事看不懂你的表結(jié)構(gòu),溝通成本極高
其實,這些問題很大程度上都可以在數(shù)據(jù)庫設(shè)計階段解決。
一、好設(shè)計 VS 壞設(shè)計
先看一個反面教材:
CREATE TABLE user (
id INT PRIMARY KEY,
name VARCHAR(255),
phone VARCHAR(50),
email VARCHAR(255),
address VARCHAR(500),
hobby VARCHAR(500),
friend_list VARCHAR(1000),
register_time DATETIME,
last_login_time DATETIME,
login_count INT,
is_delete TINYINT
);
這個設(shè)計有什么問題?
- 一個
friend_list字段存了所有好友ID,用逗號分隔 hobby也是用逗號分隔的多個愛好is_delete字段命名不規(guī)范- 所有字段都允許NULL,沒有默認(rèn)值
- 沒有任何索引
這樣的設(shè)計,項目初期可能沒問題,但隨著數(shù)據(jù)量增長,會帶來無數(shù)麻煩。下面,我們就從幾個核心原則開始,學(xué)習(xí)正確的設(shè)計方法。
二、數(shù)據(jù)庫設(shè)計的五大黃金原則
1. 規(guī)范化設(shè)計
什么是規(guī)范化?
簡單說,就是把相關(guān)數(shù)據(jù)拆分到不同的表中,避免數(shù)據(jù)冗余和異常。
第三范式(3NF)通俗解釋:
每張表只描述一個主題,并且所有字段都必須直接依賴于主鍵。
例子:
錯誤設(shè)計:
CREATE TABLE order (
order_id INT,
customer_name VARCHAR(100),
customer_phone VARCHAR(20),
product_name VARCHAR(100),
product_price DECIMAL(10,2),
order_date DATETIME
);
正確設(shè)計:
CREATE TABLE customer (
customer_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
phone VARCHAR(20) NOT NULL
);
CREATE TABLE product (
product_id INT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price DECIMAL(10,2) NOT NULL
);
CREATE TABLE order (
order_id INT PRIMARY KEY,
customer_id INT NOT NULL,
order_date DATETIME NOT NULL,
total_amount DECIMAL(12,2) NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customer(customer_id)
);
CREATE TABLE order_item (
item_id INT PRIMARY KEY,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10,2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES order(order_id),
FOREIGN KEY (product_id) REFERENCES product(product_id)
);
為什么這樣做更好?
- 當(dāng)客戶信息變更時,只需修改一處
- 產(chǎn)品價格變化不會影響歷史訂單記錄
- 避免了數(shù)據(jù)不一致問題
- 查詢更高效,特別是當(dāng)需要匯總統(tǒng)計時
2. 命名規(guī)范:讓人一眼看懂
好的命名就像一本說明書,讓團(tuán)隊協(xié)作更順暢。遵循以下規(guī)則:
- 表名:使用復(fù)數(shù)名詞,如
users,orders,products - 字段名:使用下劃線分隔,如
created_at,updated_at - 主鍵:統(tǒng)一使用
id,或者表名_id,如user_id - 外鍵:使用
關(guān)聯(lián)表名_id,如user_id,product_id - 布爾字段:使用
is_或has_前綴,如is_deleted,has_paid - 時間字段:統(tǒng)一使用
created_at,updated_at,deleted_at
反例:
u_name,phonenumb,delFlagcreateTime,lastModifyTime(混用駝峰和下劃線)
正例:
user_name,phone_number,is_deletedcreated_at,updated_at
3. 字段類型選擇:精準(zhǔn)匹配數(shù)據(jù)
選擇合適的數(shù)據(jù)類型不僅節(jié)省空間,還能提高查詢效率。下面是一些常見場景的最佳實踐:
| 數(shù)據(jù)類型 | 適用場景 | 示例 | 避免的錯誤 |
|---|---|---|---|
| TINYINT(1) | 布爾值(是/否) | is_active TINYINT(1) DEFAULT 1 | 用VARCHAR存"yes"/"no" |
| INT | 一般ID、數(shù)量 | user_id INT, view_count INT | 用BIGINT存小范圍數(shù)據(jù) |
| BIGINT | 大型系統(tǒng)ID、高并發(fā)計數(shù)器 | order_id BIGINT | 小型應(yīng)用過度使用 |
| VARCHAR(N) | 長度可變的字符串,N應(yīng)合理設(shè)置 | name VARCHAR(50) | 所有字符串都用VARCHAR(255) |
| CHAR(N) | 固定長度字符串 | country_code CHAR(2) | 用CHAR存變長內(nèi)容 |
| DECIMAL(M,D) | 金額、精確小數(shù) | price DECIMAL(10,2) | 用FLOAT/DOUBLE存金額 |
| DATETIME | 需要日期和時間 | created_at DATETIME | 用字符串存時間 |
| TIMESTAMP | 需要自動更新的時間戳 | updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | 混淆DATETIME和TIMESTAMP |
| ENUM | 固定選項(謹(jǐn)慎使用) | status ENUM('pending','paid','shipped') | 選項經(jīng)常變化的場景 |
| JSON | 確實需要存儲結(jié)構(gòu)化但不常查詢的數(shù)據(jù) | config JSON | 代替關(guān)系型設(shè)計 |
重點提醒: 金額一定要用DECIMAL,而不是FLOAT/DOUBLE!后者在計算時可能有精度丟失問題。
4. 索引設(shè)計:加速查詢
索引就像書的目錄,能讓數(shù)據(jù)庫快速定位數(shù)據(jù)。但索引不是越多越好,每個索引都會增加寫入開銷。
基本原則:
- 為經(jīng)常用于WHERE條件的字段創(chuàng)建索引
- 為JOIN操作的關(guān)聯(lián)字段創(chuàng)建索引
- 為ORDER BY和GROUP BY的字段考慮索引
- 單表索引數(shù)量不宜過多(通常不超過5個)
常見場景:
-- 用戶經(jīng)常按用戶名和郵箱搜索 CREATE INDEX idx_user_name ON users(name); CREATE INDEX idx_user_email ON users(email); -- 訂單表經(jīng)常按用戶ID和創(chuàng)建時間查詢 CREATE INDEX idx_order_user_time ON orders(user_id, created_at); -- 商品表經(jīng)常按分類和價格排序 CREATE INDEX idx_product_category_price ON products(category_id, price);
索引使用注意事項:
- 索引列不要用函數(shù)或表達(dá)式,如
WHERE YEAR(create_time)=2023 - 避免在索引列上使用NOT、<>、!=
- LIKE查詢中,'%xxx'不會使用索引,'xxx%'會使用
- 聯(lián)合索引要注意最左前綴原則
5. 軟刪除 和 硬刪除:數(shù)據(jù)安全策略
硬刪除: 直接從數(shù)據(jù)庫移除記錄
DELETE FROM users WHERE id = 1001;
軟刪除: 標(biāo)記記錄為已刪除,實際數(shù)據(jù)保留在數(shù)據(jù)庫中
ALTER TABLE users ADD COLUMN is_deleted TINYINT(1) DEFAULT 0; ALTER TABLE users ADD COLUMN deleted_at DATETIME DEFAULT NULL; -- "刪除"操作 UPDATE users SET is_deleted = 1, deleted_at = NOW() WHERE id = 1001;
軟刪除的優(yōu)勢:
- 數(shù)據(jù)可恢復(fù),降低誤操作風(fēng)險
- 保留歷史記錄,便于審計
- 保證關(guān)聯(lián)數(shù)據(jù)完整性
- 便于數(shù)據(jù)分析
什么情況下用軟刪除?
- 核心業(yè)務(wù)數(shù)據(jù)(用戶、訂單、交易記錄)
- 有法律合規(guī)要求的數(shù)據(jù)
- 需要保留歷史狀態(tài)的數(shù)據(jù)
什么情況下用硬刪除?
- 臨時數(shù)據(jù)、緩存數(shù)據(jù)
- 敏感數(shù)據(jù)需要徹底清除
- 存儲空間極度緊張且數(shù)據(jù)價值低
三、高級技巧:為未來做準(zhǔn)備
1. 預(yù)留擴(kuò)展字段
為未來可能的需求變化預(yù)留一些通用字段:
ALTER TABLE users ADD COLUMN ext_info JSON COMMENT '擴(kuò)展信息,存儲不常用字段', ADD COLUMN version INT DEFAULT 1 COMMENT '樂觀鎖版本號';
注意: 不要過度使用擴(kuò)展字段,它只適用于少量不常用且無需查詢的屬性。
2. 分庫分表的前期準(zhǔn)備
即使初期不需要分庫分表,也可以提前做些準(zhǔn)備:
- 使用BIGINT作為主鍵類型
- 避免自增ID,考慮使用雪花算法等分布式ID
- 業(yè)務(wù)字段避免跨分片JOIN
- 考慮按時間或業(yè)務(wù)維度設(shè)計分片鍵
3. 事務(wù)邊界設(shè)計
在設(shè)計表結(jié)構(gòu)時,就要考慮事務(wù)邊界:
-- 轉(zhuǎn)賬操作需要保證原子性 UPDATE accounts SET balance = balance - 100 WHERE user_id = 1001; UPDATE accounts SET balance = balance + 100 WHERE user_id = 1002;
良好的表設(shè)計應(yīng)該讓一個業(yè)務(wù)操作盡可能在一個事務(wù)內(nèi)完成,避免分布式事務(wù)。
四、評論系統(tǒng)的設(shè)計
假設(shè)我們要設(shè)計一個文章評論系統(tǒng),支持多級評論(評論可以回復(fù)評論),應(yīng)該如何設(shè)計?
需求分析:
- 用戶可以對文章發(fā)表評論
- 評論可以被回復(fù),形成多級結(jié)構(gòu)
- 需要統(tǒng)計每篇文章的評論數(shù)量
- 需要支持點贊功能
- 需要支持敏感詞過濾
設(shè)計思路:
實體分析:用戶、文章、評論、點贊
關(guān)系分析:
- 一個用戶可以有多條評論
- 一條評論屬于一篇文章
- 一條評論可以有多個回復(fù)
- 一條評論可以有多個點贊
表結(jié)構(gòu)設(shè)計:
CREATE TABLE articles (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
user_id BIGINT NOT NULL,
comment_count INT DEFAULT 0 COMMENT '評論數(shù)量,冗余字段提高查詢效率',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
is_deleted TINYINT(1) DEFAULT 0
) COMMENT='文章表';
CREATE TABLE comments (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
article_id BIGINT NOT NULL COMMENT '所屬文章ID',
user_id BIGINT NOT NULL COMMENT '評論用戶ID',
parent_id BIGINT DEFAULT 0 COMMENT '父評論ID,0表示一級評論',
content VARCHAR(1000) NOT NULL COMMENT '評論內(nèi)容',
like_count INT DEFAULT 0 COMMENT '點贊數(shù)量',
depth TINYINT DEFAULT 1 COMMENT '評論深度,1表示一級評論',
path VARCHAR(255) DEFAULT '' COMMENT '路徑,格式: 0,10,25 表示層級關(guān)系',
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
is_deleted TINYINT(1) DEFAULT 0,
KEY idx_article (article_id, created_at),
KEY idx_parent (parent_id),
FOREIGN KEY (article_id) REFERENCES articles(id)
) COMMENT='評論表';
CREATE TABLE comment_likes (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
comment_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uniq_comment_user (comment_id, user_id),
FOREIGN KEY (comment_id) REFERENCES comments(id)
) COMMENT='評論點贊表';
設(shè)計說明:
- 評論表使用了
parent_id+depth+path的組合,支持高效查詢評論樹 path字段存儲層級路徑,如0,10,25表示ID為25的評論是ID為10的評論的子評論article.comment_count是冗余字段,用于避免每次查詢都COUNT- 點贊表使用唯一索引防止重復(fù)點贊
- 所有表都有軟刪除標(biāo)記和時間戳
查詢所有一級評論及直接回復(fù):
SELECT c1.*, c2.* FROM comments c1 LEFT JOIN comments c2 ON c2.parent_id = c1.id AND c2.depth = 2 WHERE c1.article_id = 100 AND c1.depth = 1 AND c1.is_deleted = 0 ORDER BY c1.created_at DESC, c2.created_at ASC;
這種設(shè)計既支持高效查詢,又能適應(yīng)業(yè)務(wù)變化,是經(jīng)過實戰(zhàn)檢驗的可靠方案。
五、工具推薦:提高設(shè)計效率
數(shù)據(jù)庫設(shè)計工具:
- MySQL Workbench(免費)
- Navicat Data Modeler(付費)
- dbdiagram.io(在線免費)
SQL規(guī)范檢查:
- Alibaba Java Coding Guidelines(包含SQL規(guī)范)
- SonarQube(支持SQL質(zhì)量檢查)
版本管理:
- Flyway
- Liquibase
- 一定要使用版本控制管理數(shù)據(jù)庫變更腳本!
六、總結(jié)
- 規(guī)范化是基礎(chǔ):遵循第三范式,避免數(shù)據(jù)冗余
- 命名要一致:統(tǒng)一的命名規(guī)范是團(tuán)隊協(xié)作的基石
- 類型要精準(zhǔn):根據(jù)實際需求選擇最合適的數(shù)據(jù)類型
- 索引要克制:只為必要的查詢條件創(chuàng)建索引
- 軟刪除更安全:核心業(yè)務(wù)數(shù)據(jù)優(yōu)先考慮軟刪除
- 為未來留余地:考慮擴(kuò)展性,但不要過度設(shè)計
- 文檔不可少:每個表、每個字段都要有清晰的注釋
以上就是一文詳解MySQL數(shù)據(jù)表設(shè)計的五大黃金原則的詳細(xì)內(nèi)容,更多關(guān)于MySQL數(shù)據(jù)表設(shè)計的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Win10系統(tǒng)下MySQL8.0.16 壓縮版下載與安裝教程圖解
這篇文章主要介紹了Win10系統(tǒng)下MySQL8.0.16 壓縮版下載與安裝教程圖解,本文圖文并茂給大家介紹的非常詳細(xì),具有一定的參考解決價值,需要的朋友可以參考下2019-06-06
MySQL存儲過程中游標(biāo)循環(huán)的跳出和繼續(xù)操作示例
這篇文章主要介紹了MySQL存儲過程中游標(biāo)循環(huán)的跳出和繼續(xù)操作示例,解決了在MySQL存儲過程中循環(huán)時執(zhí)行游標(biāo)的一個conitnue的操作解決方法,需要的朋友可以參考下2014-07-07
VS2019連接mysql8.0數(shù)據(jù)庫的教程圖文詳解
這篇文章主要介紹了VS2019連接mysql8.0數(shù)據(jù)庫的教程,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05

