主鍵 (PK) 與外鍵 (FK) 是關聯式資料庫完整性的基石,但實務上常被誤解或濫用。
主鍵 (Primary Key)
- 定義: 唯一識別資料列的欄位或欄位組合,非空、唯一。
- 作用: 實體完整性、叢集索引預設鍵 (InnoDB/SQL Server)、外鍵參考目標、高效單列查詢/聯結。
- 選擇原則: 偏好 代理鍵 (自增整數 BIGINT/INT, UUID) 而非業務鍵 (身分證、Email),因業務鍵可能變更、長度大、語意耦合。
- 效能: 高選擇性 (選擇度 ≈ 1),B+ 樹索引極窄、極快。
外鍵 (Foreign Key)
- 定義: 子表欄位參考父表主鍵/唯一鍵,強制 參考完整性 (無孤兒列、級聯刪除/更新)。
- 優點: 資料庫層面保證一致性、文檔化關聯、防止應用層 Bug 產生髒資料。
- 缺點 (高併發/大數據量慎用):
- 鎖爭用:修改父表 PK 或子表 FK 時需鎖定兩表,降低並行度。
- 級聯操作不可控:刪除父列觸發大量子列刪除,導致長鎖定、複寫延遲、觸發器連鎖。
- Schema 變更困難:需先卸除 FK 再修改欄位/索引,維護窗口長。
- 分庫分表不支援:跨實例無法建立 FK。
實務建議:應用層守護完整性
- 核心交易系統 (訂單、庫存、帳戶): 保留 FK,資料正確性優先於寫入吞吐。
- 高寫入、可容忍最終一致性 (日誌、行為、推薦): 移除 FK,應用層透過「先寫父、再寫子、補償機制」保證。
- 統一規範: 所有關聯欄位**必須建立索引** (即使無 FK),否則聯結查詢全表掃描。
- 軟刪除: 僅更新
is_deleted 標記,避免級聯刪除風暴。
範例:應用層模擬 FK 檢查
// 建立訂單前驗證用戶存在
if (!userRepo.exists(order.getUserId())) throw new BizException("用戶不存在");
orderRepo.insert(order);
// 刪除用戶前檢查關聯訂單
if (orderRepo.countByUserId(userId) > 0) throw new BizException("存在關聯訂單,不可刪除");
// 或軟刪除
userRepo.softDelete(userId);
理解 PK/FK 的權衡,才能在正確性與效能之間做出架構決策。