依據資料庫的第二範式,資料庫中每一個表中都需要有一個唯一的主鍵,其他資料元素和主鍵一一對應。
那麼關于主鍵的選擇就成為一個關鍵點了,一般有如下方案:
使用業務字段作為主鍵
比如說對于使用者表來說,可以使用手機号,email或者身份證号作為主鍵。對大部分場景,這并不适用,像評論表,你很難找到一個業務字段主鍵。而對于使用者表,考慮的是業務字段是否能夠唯一辨別人,一人可有多個email和手機号,一旦出現變更email或手機号,就需要變更所有引用的外鍵資訊,是以使用email或者手機作為主鍵不行。
身份證号碼确實是使用者唯一辨別,但由于過于私密,并非使用者系統的必須屬性,你的系統如果沒有要求做實名認證,肯定不會要求使用者填寫身份證号。
而且已有身份證号碼也會變化,比如1999年時身份證号從15位變為18位,但主鍵一變更,以該主鍵為外鍵的表也都要随之變更,影響很大。
使用生成的唯一ID作為主鍵
是以,更推薦使用生成的ID作為資料庫主鍵。不僅是因為其唯一性,且一旦生成就不會變更,可随意引用。
1 資料庫自增id
提供一個專門用于生成主鍵的庫,這樣服務每次接收請求都
- 先往單點庫的某表裡插入一條沒啥業務含義的資料
- 然後擷取一個資料庫自增id
- 取得id後,再寫入對應的分庫分表
優點
簡單,是個人都會
缺點
因為是單庫生成自增id,是以若是高并發場景,有性能瓶頸。
若硬是要改進,那就專門開個服務:
- 該服務每次就拿到目前id最大值
- 然後自己遞增幾個id,一次性傳回一批id
- 然後再把目前最大id值修改成遞增幾個id之後的一個值
但無論怎麼說都隻是基于單庫。
适用場景
分庫分表原因其實就倆:
- 單庫的并發負載過高
- 單庫的資料量過大
除非并發不高,但資料量太大導緻的分庫分表擴容,可用該方案,因為可能每秒最高并發最多就幾百,那麼就走單獨的一個庫和表生成自增主鍵即可。
并發很低,幾百/s,但是資料量大,幾十億的資料,是以需要靠分庫分表來存放海量資料。
當資料庫分庫分表後,使用自增字段就無法保證 ID 的全局唯一性了嗎?
1.使用資料庫的自增,設定起始值和步長不一樣,不是一樣可以實作嗎?
2.預估每天的資料量,預先生成ID存入緩存(比如Redis)裡面,然後去取,這種方法也簡單?
但是這其實很難預估資料量,某一天有活動咋辦?不同的起始值也可,隻是增加人工成本,增加了庫表咋辦?忘了設定咋辦?
2 UUID(Universally Unique Identifier,通用唯一辨別碼)
2.1 優點
本地生成,不依賴任何第三方系統,是以在性能和可用性上都比較好。
2.2 缺點
2.2.1 無序
生成的ID做好具有單調遞增性,即有序。
為什麼ID要有序呢?
因為在系統設計時,ID可能成為排序字段。
比如實作評論系統,一般會設計兩個表:
-
評論表
存儲評論的詳細資訊,其中有ID字段,有評論的内容,還有評論人ID,被評論内容的ID等等,以ID字段作為分區鍵
-
評論清單
存儲着内容ID和評論ID的對應關系,以内容ID為分區鍵
擷取内容的評論清單時,需按照時間序倒排,因為ID時間上有序,是以可按評論ID倒序排列。
若評論ID不在時間上有序,就得在評論清單中再備援createTime列以排序,假設内容ID、評論ID和時間都8位元組,就要多出50%存儲空間存儲時間字段,浪費存儲空間。
ID有序會提升資料的寫性能
MySQL InnoDB主鍵也是一種索引。索引資料在B+樹中有序排列。當插入的下一條記錄ID遞增時,DV隻需将其追加到後面。
但若插入資料無序,則DB查找資料應該插入的位置,再挪動該資料後面的資料,造成多餘資料移動開銷。
導緻 B+ 樹索引寫時有着過多的随機寫操作,而機械磁盤:
- 随機寫時,需先“尋道”找到要寫入位置,即讓磁頭找到對應磁道,很耗時
- 順序寫就無需尋道,大大提升索引寫性能
寫時不能産生有順序的 append 操作,而需要 insert,将會讀取整個 B+ 樹節點到記憶體,在插入這條記錄後會将整個節點寫回磁盤,這種操作在記錄占用空間較大情況下,性能下降明顯
2.2.2 過長
由32個16進制數字組成的字元串,若作為DB主鍵使用,較耗費空間。
2.2.3 不具備業務含義
現實使用的ID中都包含有一些有意義資料,這些資料會出現在ID的固定位置。
如身份證:
- 前6位地區編号
-
7~14生日
不同城市電話号碼的區号不同,前三位即可看出所屬營運商。
而若生成的ID可被反解,則從反解出的資訊中即可驗證ID,進而知道該ID生成時間、從哪個機房發号器生成、為哪個業務服務,這都有助問題排查。
Snowflake算法則可完美彌補UUID缺點。
随機生成檔案名、編号等,生成Request ID标記單次請求。
3 系統時間
擷取目前時間即可。但問題是高并發時,會有重複,這肯定不合适啊,而且還可能修改系統時間!
若用該方案,一般将目前時間跟很多其他的業務字段拼接起來,作為一個id。若業務上你可以接受,那也行。
你可以将别的業務字段值跟目前時間拼接起來,組成一個全局唯一的編号,比如訂單編号:
時間戳 + 使用者id + 業務含義編碼
。