淺談資料庫主鍵策略
資料庫表的主鍵很多童鞋都非常熟悉了,主鍵就是Primary Key,簡稱PK。
資料庫主鍵的作用是唯一標識一條記錄,所以在同一張表中,任意一條記錄的主鍵都是唯一的,不然,資料庫系統就無法根據主鍵直接定位記錄。
雖然資料庫系統本身對主鍵沒有特別的要求,但是,寫程式的時候,要考慮清楚使用什麼型別的主鍵。正確地使用主鍵是儲存資料成功的一半,錯誤地使用主鍵會讓一個應用逐漸走向崩潰。
主鍵不可修改
對於資料庫來說,主鍵其實是可以修改的,只要不和其他主鍵衝突就可以。但是,對於應用來說,如果一條記錄要修改主鍵,那就會出大問題。
因為主鍵的第二個作用是讓其他表的外來鍵引用自己,從而實現關係結構。一旦某個表的主鍵發生了變化,就會導致所有引用了該表的資料必須全部修改外來鍵。很多Web應用的資料庫並不是強約束(僅僅引用主鍵但並沒有設定外來鍵約束),修改主鍵會導致資料完整性直接被破壞。
業務欄位不可用於主鍵
所有涉及到業務的欄位,無論它看上去是否唯一,都決不能用作主鍵。例如,使用者表的Email欄位是唯一的,但是,如果用它作主鍵,就會導致其他表到處引用Email欄位,從而洩露使用者資訊。
此外,修改Email實際上是一個業務操作,這個操作就直接違反了上一條原則。
那麼,主鍵應該使用哪個欄位呢?
主鍵必須使用單獨的,完全沒有業務含義的欄位,也就是主鍵本身除了唯一標識和不可修改這兩個責任外,主鍵沒有任何業務含義。
類似的,看上去唯一的使用者名稱、身份證號等,也不能用作主鍵。對這些唯一欄位,應該加上unique索引約束。
主鍵應該用什麼型別
主鍵應該使用整數還是字串?(用浮點數的請自覺充值智商)
我強烈建議使用字串。
為什麼?
我們先看使用整數的問題。
使用整數有兩個選擇:資料庫自增和自己生成。
自己生成其實也是自增,無非就是把上次使用的值儲存到某個地方,下次使用的時候繼續自增。常見的做法是用一個單獨的表儲存上次用的最大值。這種方式實現複雜,可靠性低,還不如資料庫自增。
資料庫自增最大的問題還不在於資料庫單點造成無法水平切分,因為絕大部分公司還撐不到業務需要分庫的情況就倒閉了。
自增主鍵最大的問題是把公司業務的關鍵運營資料完全暴露給了競爭對手和VC。舉個例子,使用者表採用自增主鍵,只需要每週一早上去註冊一個使用者,把上週註冊的ID和本週註冊的ID一比,立刻就知道了該公司一週的新增使用者數量。如果網站聲稱新增了10萬用戶,但ID卻只增加了1千,就只能呵呵了。
因為主鍵的本質是保證唯一記錄,並不要求主鍵是連續的。實際上不連續的更好,這樣既避免了運營資料洩露,也給黑客預測ID製造了障礙,具有更高的安全性。
用字串主鍵就不存在這個問題。如果我們用一個UUID作為主鍵,即varchar(32),除了佔用的儲存空間較多外,字串主鍵具有不可預測性。
有人覺得UUID完全隨機,主鍵本身沒有按時間遞增,不利於直接主鍵排序。其實解決這個問題很簡單。
方法一,直接用時間戳+UUID構造一個主鍵,時間戳注意補0,這樣生成的主鍵就是按時間排序的。這個方法簡單粗暴,缺點是主鍵更長了。
方法二,自定義一個演算法,時間戳放高位,序列號放低位,還可以保留機器位,然後用base32編碼,可以把長度控制在20個字元內。
有人會問,根據方法二,構造包含時間戳和序列號的64位整數作為主鍵是否可行?
理論上來說是可行的,因為時間戳0xffffffff可以表示到2100年。但是剩下的位不是ffffffff而是隻有fffff,如果給機器分配ff作為標識,那麼每秒只能最多生成0xfff+1=4096個主鍵,對一些大型應用不太夠用。
為啥64位整數除掉時間戳只能用後面的fffff位呢?這是因為JavaScript的Number型別是56位精度,它能表示的最大整數是0x1fffffffffffff,而我們遲早會用REST跟JavaScript打交道,所以要把64位整數的範圍限制在0x1fffffffffffff內,否則與JavaScript互動就會出錯。
雖然理論上64位整數做時間戳+序列號的主鍵是沒問題的,但是實踐中是沒法繞開與JavaScript互動的,綜合考慮,字串主鍵最可靠。