親身經歷

最近接了個外包,和另外兩個哥們一起開發。因為他們的時間更充裕,所以前期的表結構都是他們來設計,我沒有參與。等我空下來開始做我的部分時,兩眼一抹黑,光看表名完全不知道是什麼東西。

我不理解,但是大受震撼。

比如說:

DC_COURSE_TESTPAPER          課程下的問卷
DC_COURSE_PAPER_HISTORY      問卷與學生的關聯表,也就是問卷下發給哪些學生  
DC_COURSE_PAPER_HISTORY_ALL  問卷與問題的關聯表,也就是問卷包含哪些問題

光看表名能知道是什麼意思嗎?難受啊兄弟們。

這讓我深刻意識到了:哪怕是一些“高級開發”,也並不知道怎麼去設計一個好的表結構。

於是決定花點時間寫一篇文章,和大家一起探討如何更好的設計表結構。

所有觀點都是我結合多年的經驗得來,不一定正確,如有錯誤之處歡迎大家指正。

image

表名:第一眼就要知道是幹什麼的

1. 有意義的前綴 + 清晰的表名

前綴在大型系統中是有必要的,可以區分不同業務模塊,但關鍵是前綴要有明確含義,表名要語義清晰。

❌ 不好的命名

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

區別在哪裡?

  • 不好的:縮寫讓人猜測,TESTPAPER、INFO、DTL這些詞彙模糊不清
  • 好的:前綴有明確業務含義,表名用完整英文詞彙表達準確含義

什麼時候需要前綴?

  • 多個業務系統共用資料庫:USER_, ORDER_, PRODUCT_
  • 區分不同數據類型:LOG_, CONFIG_, TEMP_
  • 大型項目的模塊劃分:CRM_, ERP_, CMS_

2. 用完整的英文單詞而不是拼音

❌ 不好的命名

kecheng_wenjuan         # 拼音
user_xinxi             # 中英混合
訂單_items             # 中英混合

✅ 好的命名

course_questionnaires  # 純英文,語義清晰
user_profiles         # 純英文
order_items          # 純英文

原因

  • 英文是編程的通用語言,團隊成員更容易理解
  • 避免編碼問題

在一些專業且複雜的業務系統(比如醫療行業)中,會提倡會拼音來作為欄位名,但是表名還是英文為主,這個後面展開講。

3. 表名要體現業務含義,不要只是技術實現

❌ 不好的命名

data_table_001
temp_storage
middle_table
relation_mapping

✅ 好的命名

student_scores        # 學生成績
file_uploads         # 文件上傳記錄
course_enrollments   # 課程報名
user_preferences     # 使用者偏好設定

除非是臨時用的表,不參與任何業務邏輯,只是用來做數據處理或者測試。

欄位命名:見名知意

1. 布林欄位用 is_ 開頭

❌ 不好的命名

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) 更容易理解

2. 時間欄位統一後綴

❌ 不好的命名

create_time
update_date
delete_at
register_datetime

✅ 好的命名

created_at    # 創建時間
updated_at    # 更新時間
deleted_at    # 刪除時間
registered_at # 註冊時間

好處

  1. 一眼就能看出是時間欄位 - 看到 created_at 就知道這是時間類型,不用去查表結構
  2. 避免命名混亂 - 有的用 _time,有的用 _date,有的用 _datetime,團隊裡每個人習慣不一樣,最後搞得亂七八糟
  3. _at 在英語裡表示"在某個時間點",比 _time 更準確
  4. 邏輯刪除的最佳實踐 - 特別推薦用 deleted_at 作邏輯刪除欄位。這樣設計有幾個好處:
    • 能看出來刪除的具體時間
    • 可以追蹤刪除操作的歷史
    • 支持數據恢復(把 deleted_at 設為 NULL 就行)
    • 比用 is_deleted 這種布林欄位更靈活

額外經驗

除了基本的命名規範,還有一些實用的經驗:

  1. 邏輯刪除用時間欄位 - 用 deleted_atis_deleted 好,能看出來刪除時間,支持數據恢復和歷史追蹤
  2. 狀態欄位用枚舉 - 不要用數字 1、2、3 表示狀態,用 status 欄位,值用 'pending''approved''rejected' 這樣的英文單詞
  3. 金額欄位用 decimal - 不要用 floatdouble,用 decimal(10,2) 這樣的類型,避免浮點數精度問題
  4. 密碼欄位要加密 - 密碼欄位名用 password_hashencrypted_password,不要直接叫 password
  5. 軟刪除要加索引 - 如果經常查詢未刪除的數據,給 deleted_at 欄位加索引,提高查詢性能

3. 外鍵欄位統一 _id 後綴

❌ 不好的命名

user          # 這是用戶ID還是用戶對象?
course        # 課程ID?
teacher_key   # 什麼key?

✅ 好的命名

user_id       # 用戶ID
course_id     # 課程ID
teacher_id    # 教師ID

這個好處,應該不用過多贅述了。

4. 額外的經驗

除了上面這些基本的命名規範,還有一些實用的經驗:

  1. 邏輯刪除用時間欄位 - 用 deleted_atis_deleted 更好,不僅能看出來刪除時間,還能用於數據恢復和歷史追蹤
  2. 狀態欄位用枚舉 - 不要用數字 1、2、3 表示狀態,用 status 欄位,值用 'pending''approved''rejected' 這樣的英文單詞
  3. 金額欄位用 decimal - 不要用 floatdouble,用 decimal(10,2) 這樣的類型,避免浮點數精度問題
  4. 密碼欄位要加密 - 密碼欄位名用 password_hashencrypted_password,不要直接叫 password

表結構設計:關係清晰、適度冗餘

1. 一對多關係:外鍵放在多的一邊

讓我們用用戶和訂單的業務關係來舉例:

用戶表 (users)

欄位名 類型 說明
id BIGINT 主鍵
username VARCHAR(50) 用戶名
email 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 創建時間

這樣設計的好處:

  • 通過 user_id 就知道訂單屬於哪個用戶
  • JOIN 一下就能拿到用戶的所有訂單
  • 新增訂單欄位不影響用戶表

2. 多對多關係:中間表命名要體現關係

多對多關係的中間表命名要根據具體情況來選擇:

情況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

如何選擇命名?

  • 有業務含義的關係:用具體的業務名詞,如enrollmentsordersfriendships
  • 純粹的映射關係:可以用mappingsrelations或直接用實體1_實體2s
  • 關鍵是保持團隊內命名風格的統一

3. 適當的欄位冗餘:提升查詢效率

有時候為了避免複雜的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?

  • 查詢用戶的所有購買記錄時,直接查 order_items 表就行
  • 不需要先通過 orders 表再關聯到 order_items
  • 一個查詢代替了兩表JOIN

商品評論表 (product_reviews)

欄位名 類型 說明
id BIGINT 主鍵
product_id BIGINT 商品ID
category_id BIGINT 商品分類ID(冗餘)
user_id BIGINT 用戶ID
rating TINYINT 評分
content TEXT 評論內容

為什麼要冗餘 category_id?

  • 按分類統計評分時,不需要JOIN商品表
  • 查詢某分類下的所有評論更高效

什麼時候該冗餘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這樣的表名寫程式碼。

最後一句話:程式碼是寫給人看的,表結構也是建給人用的。


原文出處:https://juejin.cn/post/7542499988493926440


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝11   💬6   ❤️5
472
🥈
我愛JS
📝1   💬5   ❤️4
95
🥉
AppleLily
📝1   💬4   ❤️1
56
#4
💬1  
5
#5
xxuan
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次