最近接了個外包,和另外兩個哥們一起開發。因為他們的時間更充裕,所以前期的表結構都是他們來設計,我沒有參與。等我空下來開始做我的部分時,兩眼一抹黑,光看表名完全不知道是什麼東西。
我不理解,但是大受震撼。
比如說:
DC_COURSE_TESTPAPER 課程下的問卷
DC_COURSE_PAPER_HISTORY 問卷與學生的關聯表,也就是問卷下發給哪些學生
DC_COURSE_PAPER_HISTORY_ALL 問卷與問題的關聯表,也就是問卷包含哪些問題
光看表名能知道是什麼意思嗎?難受啊兄弟們。
這讓我深刻意識到了:哪怕是一些“高級開發”,也並不知道怎麼去設計一個好的表結構。
於是決定花點時間寫一篇文章,和大家一起探討如何更好的設計表結構。
所有觀點都是我結合多年的經驗得來,不一定正確,如有錯誤之處歡迎大家指正。
前綴在大型系統中是有必要的,可以區分不同業務模塊,但關鍵是前綴要有明確含義,表名要語義清晰。
❌ 不好的命名
DC_COURSE_TESTPAPER # TESTPAPER是問卷還是試卷?
TB_USER_INFO # TB前綴無意義,INFO太泛泛
T_ORDER_DTL # DTL是detail的縮寫?
DATA_TBL_001 # 完全看不懂
✅ 好的命名
DC_COURSE_QUESTIONNAIRES # DC表示Distance Course遠程課程系統
SYS_USER_PROFILES # SYS表示系統核心模塊
ORDER_ITEMS # 訂單商品明細
LMS_STUDENT_SCORES # LMS表示Learning Management System
區別在哪裡?
什麼時候需要前綴?
USER_
, ORDER_
, PRODUCT_
LOG_
, CONFIG_
, TEMP_
CRM_
, ERP_
, CMS_
❌ 不好的命名
kecheng_wenjuan # 拼音
user_xinxi # 中英混合
訂單_items # 中英混合
✅ 好的命名
course_questionnaires # 純英文,語義清晰
user_profiles # 純英文
order_items # 純英文
原因
在一些專業且複雜的業務系統(比如醫療行業)中,會提倡會拼音來作為欄位名,但是表名還是英文為主,這個後面展開講。
❌ 不好的命名
data_table_001
temp_storage
middle_table
relation_mapping
✅ 好的命名
student_scores # 學生成績
file_uploads # 文件上傳記錄
course_enrollments # 課程報名
user_preferences # 使用者偏好設定
除非是臨時用的表,不參與任何業務邏輯,只是用來做數據處理或者測試。
❌ 不好的命名
active # 是激活還是活躍?
delete # 刪除狀態還是刪除動作?
flag # 什麼標誌?
✅ 好的命名
is_active # 是否激活
is_deleted # 是否已刪除
is_verified # 是否已驗證
也有些團隊會用if_
作為前綴,這也沒什麼毛病。
好處
用 is_
開頭有幾個明顯的好處:
is_active
就知道這個欄位要麼是 true
要麼是 false
,不用再去猜active
這樣的名字,你搞不清楚它表示的是狀態還是動作。is_active
就明確表示狀態if (user.is_active)
比 if (user.active)
更容易理解❌ 不好的命名
create_time
update_date
delete_at
register_datetime
✅ 好的命名
created_at # 創建時間
updated_at # 更新時間
deleted_at # 刪除時間
registered_at # 註冊時間
好處
created_at
就知道這是時間類型,不用去查表結構_time
,有的用 _date
,有的用 _datetime
,團隊裡每個人習慣不一樣,最後搞得亂七八糟_at
在英語裡表示"在某個時間點",比 _time
更準確deleted_at
作邏輯刪除欄位。這樣設計有幾個好處:
deleted_at
設為 NULL
就行)is_deleted
這種布林欄位更靈活額外經驗
除了基本的命名規範,還有一些實用的經驗:
deleted_at
比 is_deleted
好,能看出來刪除時間,支持數據恢復和歷史追蹤status
欄位,值用 'pending'
、'approved'
、'rejected'
這樣的英文單詞float
或 double
,用 decimal(10,2)
這樣的類型,避免浮點數精度問題password_hash
或 encrypted_password
,不要直接叫 password
deleted_at
欄位加索引,提高查詢性能❌ 不好的命名
user # 這是用戶ID還是用戶對象?
course # 課程ID?
teacher_key # 什麼key?
✅ 好的命名
user_id # 用戶ID
course_id # 課程ID
teacher_id # 教師ID
這個好處,應該不用過多贅述了。
除了上面這些基本的命名規範,還有一些實用的經驗:
deleted_at
比 is_deleted
更好,不僅能看出來刪除時間,還能用於數據恢復和歷史追蹤status
欄位,值用 'pending'
、'approved'
、'rejected'
這樣的英文單詞float
或 double
,用 decimal(10,2)
這樣的類型,避免浮點數精度問題password_hash
或 encrypted_password
,不要直接叫 password
讓我們用用戶和訂單的業務關係來舉例:
用戶表 (users)
欄位名 | 類型 | 說明 |
---|---|---|
id | BIGINT | 主鍵 |
username | VARCHAR(50) | 用戶名 |
VARCHAR(100) | 郵箱 | |
created_at | TIMESTAMP | 創建時間 |
訂單表 (orders)
欄位名 | 類型 | 說明 |
---|---|---|
id | BIGINT | 主鍵 |
user_id | BIGINT | 用戶ID(外鍵) |
order_no | VARCHAR(32) | 訂單號 |
total_amount | DECIMAL(10,2) | 總金額 |
status | VARCHAR(20) | 訂單狀態 |
created_at | TIMESTAMP | 創建時間 |
這樣設計的好處:
多對多關係的中間表命名要根據具體情況來選擇:
情況1:有業務含義的關係表 比如學生和課程的關係,不只是簡單關聯,還有報名時間、狀態等業務信息:
❌ 不好的設計
student_course_rel # rel是什麼關係?
sc_mapping # 縮寫看不懂
middle_table # 完全不知道什麼意思
✅ 好的設計
課程報名表 (course_enrollments)
欄位名 | 類型 | 說明 |
---|---|---|
id | BIGINT | 主鍵 |
student_id | BIGINT | 學生ID |
course_id | BIGINT | 課程ID |
enrolled_at | TIMESTAMP | 報名時間 |
status | VARCHAR(20) | 報名狀態 |
情況2:純粹的關聯關係表 如果只是單純的多對多映射,沒有額外的業務屬性,用mapping
也是可以的:
用戶角色關聯表 (user_role_mappings)
欄位名 | 類型 | 說明 |
---|---|---|
user_id | BIGINT | 用戶ID |
role_id | BIGINT | 角色ID |
文章標籤關聯表 (article_tag_relations)
欄位名 | 類型 | 說明 |
---|---|---|
article_id | BIGINT | 文章ID |
tag_id | BIGINT | 標籤ID |
如何選擇命名?
enrollments
、orders
、friendships
mappings
、relations
或直接用實體1_實體2s
有時候為了避免複雜的JOIN查詢,適當冗餘是非常有必要的。
最典型的就是冗餘上級ID:
訂單詳情表 (order_items)
欄位名 | 類型 | 說明 |
---|---|---|
id | BIGINT | 主鍵 |
order_id | BIGINT | 訂單ID |
user_id | BIGINT | 用戶ID(冗餘) |
product_id | BIGINT | 商品ID |
quantity | INT | 購買數量 |
price | DECIMAL(10,2) | 商品單價 |
為什麼要冗餘 user_id?
商品評論表 (product_reviews)
欄位名 | 類型 | 說明 |
---|---|---|
id | BIGINT | 主鍵 |
product_id | BIGINT | 商品ID |
category_id | BIGINT | 商品分類ID(冗餘) |
user_id | BIGINT | 用戶ID |
rating | TINYINT | 評分 |
content | TEXT | 評論內容 |
為什麼要冗餘 category_id?
什麼時候該冗餘ID?
現在我們用上文講的一些原則來重新設計開頭的表:
DC_COURSE_TESTPAPER # 什麼鬼?
DC_COURSE_PAPER_HISTORY # HISTORY是歷史?
DC_COURSE_PAPER_HISTORY_ALL # ALL又是什麼意思?
課程問卷表 (course_questionnaires)
欄位名 | 類型 | 說明 |
---|---|---|
id | BIGINT | 主鍵 |
course_id | BIGINT | 課程ID |
title | VARCHAR(200) | 問卷標題 |
description | TEXT | 問卷描述 |
status | ENUM | 狀態:draft/published/closed |
created_at | TIMESTAMP | 創建時間 |
問卷分發記錄表 (questionnaire_assignments)
欄位名 | 類型 | 說明 |
---|---|---|
id | BIGINT | 主鍵 |
questionnaire_id | BIGINT | 問卷ID |
student_id | BIGINT | 學生ID |
assigned_at | TIMESTAMP | 分發時間 |
status | ENUM | 狀態:assigned/started/completed |
問卷題目表 (questionnaire_questions)
欄位名 | 類型 | 說明 |
---|---|---|
id | BIGINT | 主鍵 |
questionnaire_id | BIGINT | 問卷ID |
content | TEXT | 題目內容 |
question_type | ENUM | 題目類型 |
sort_order | INT | 排序 |
現在再看:
course_questionnaires
- 一眼就知道是課程問卷questionnaire_assignments
- 問卷分發記錄questionnaire_questions
- 問卷題目是不是瞬間清晰了?
當然,因為業務簡單,這裡就不統一加前綴了。
資料庫表結構是專案的重中之重。
好的表設計能讓整個團隊開發起來更順暢,減少溝通成本,而且程式碼維護起來更容易,新人上手也更簡單。
投入時間做好表結構設計,絕對是值得的投資。
沒有任何人願意每天面對DC_COURSE_PAPER_HISTORY_ALL
這樣的表名寫程式碼。
最後一句話:程式碼是寫給人看的,表結構也是建給人用的。