主鍵設計是資料模型的基石,直接影響效能、擴充性、維護成本。本文針對「業務天然鍵」與「無意義代理鍵」的長期爭論,從儲存冗餘、連接複雜度、分頁效能、複寫衝突等維度深度剖析,並給出落地實施方案。
傳統設計常直接使用業務欄位(單號、身分證、商品編碼)或其組合(聯集鍵)作為主鍵:
ORDER BY col1, col2, col3 無法有效利用覆蓋索引。OFFSET/FETCH 或 ROW_NUMBER(),游標式分頁邏輯極其複雜。引入無業務含義的 BIGINT IDENTITY 或自建序列作為主鍵(Surrogate Key):
CREATE TABLE Parent (
ID BIGINT NOT NULL PRIMARY KEY NONCLUSTERED, -- 代理鍵
BizCode VARCHAR(30) NOT NULL UNIQUE, -- 業務鍵加唯一約束
...
)
CREATE TABLE Child (
ID BIGINT NOT NULL PRIMARY KEY NONCLUSTERED,
ParentID BIGINT NOT NULL REFERENCES Parent(ID), -- 單欄位 FK
...
)
| 特性 | IDENTITY | 自建序列表 |
|---|---|---|
| 控制權 | 完全由引擎控制,不可手動指定 | 應用層可控,支援跨庫、跨表、預取、重置 |
| 發布/訂閱 | 需管理身分範圍,易衝突 | 天然支援多主節點分配不重疊區間 |
| 資料恢復/合併 | 身分值衝突風險高 | 可手動調整序列值,彈性大 |
| 取值時機 | INSERT 後才能取得 | INSERT 前即可預取,利於批次邏輯 |
| 實作複雜度 | 零成本 | 需建立序列表、函數、並發控制 |
建議: 中小型系統直接用 IDENTITY(1,1);大型分散式、需跨庫合併、多主寫入場景,投資自建序列(參考 Oracle Sequence)長期受益。
改用代理鍵後,原業務鍵必須加上 UNIQUE CONSTRAINT 或 UNIQUE INDEX 保證語義唯一:
ALTER TABLE Parent ADD CONSTRAINT UQ_Parent_BizCode UNIQUE NONCLUSTERED (BizCode);
-- 或
CREATE UNIQUE INDEX IX_Parent_BizCode ON Parent(BizCode);
應用層亦需保留重複檢查邏輯(先查後插或捕獲 2627 錯誤),雙重保險。
代理鍵通常為遞增、不更新、高選擇性,不適合做叢集索引。叢集索引應留給:
OrderDate、CreatedAt)代理鍵主鍵建立為 NONCLUSTERED,叢集索引另行設計,實現「主鍵 ≠ 叢集索引」的進階模式。
-- 利用代理鍵單調遞增特性,鍵集分頁,無需 OFFSET 掃描
DECLARE @PageSize INT = 50, @LastSeenID BIGINT = 0;
SELECT TOP (@PageSize) ID, BizCode, Col1, Col2
FROM Parent
WHERE ID > @LastSeenID
ORDER BY ID; -- 極速,索引覆蓋,無排序運算子
此模式在千萬列表下仍能亞毫秒響應,是高併發 Web API 的標準解法。
SurrogateID BIGINT IDENTITY(1,1) 欄位,填充既有資料。UNIQUE INDEX 保留語義。ParentSurrogateID,回填對應值,建立單欄 FK。代理鍵並非銀彈,但對於「主從層級深、資料量大、高併發、需分頁、跨系統整合」的現代應用,是降低系統複雜度的最穩健選擇。