Mysql:小主鍵,大問題

今日格言:讓一切迴歸原點,迴歸最初的爲何。mysql

本篇講解 Mysql 的主鍵問題,從爲何的角度來了解 Mysql 主鍵相關的知識,並拓展到主鍵的生成方案問題。不再怕被問到 Mysql 時只知道 CRUD 了。算法

1、爲何須要主鍵

  1. 數據記錄需具備惟一性(第一範式)
  2. 數據須要關聯 join
  3. 數據庫底層索引用於檢索數據所需

如下廢話連篇,能夠直接跳過到下一節。sql

信息是用來消除隨機不定性的東西」(香農)。人經過得到、識別天然界和社會的不一樣信息來區別不一樣事物,得以認識和改造世界。數據是反映客觀事物屬性的記錄,是信息的具體表現形式。數據通過加工處理以後,就成爲信息;而信息須要通過數字化轉變成數據才能存儲和傳輸。數據庫就是用於存儲數據記錄的。既已如此,記錄即是具備肯定性(相對)的信息,其肯定性即惟一性。咱們得出第一條緣由:mongodb

1.數據記錄需具備惟一性數據庫

世界是由客觀存在及其關係組成的。數據是數字化和模型化的存在關係。數據除了自己的描述價值外,其價值還在於其相互關聯性。爲實現關聯的準確性,數據須要有對外相互關聯的標識。因此體如今數據存儲上,主鍵的第二做用,也是存在的第二因素即:c#

2.數據須要關聯數據結構

數據用於描述客觀實在的,自己沒有意義。只有在根據主觀需求組織以後,經過必定方式知足人認識事物的過程才具備了意義。因此數據須要被檢索,被組織。則主鍵第三個做用:併發

3.數據庫底層索引用於檢索數據所需分佈式

2、爲何主鍵不宜過長

這個問題的點在上。那有什麼優點?(嘿嘿嘿,內涵)—— 短不佔空間。但這麼點磁盤空間相對整個數據量來講微不足道,並且咱們通常不怎麼用到主鍵列。那麼緣由應該在上,並且和原始數據關係不大。以此天然得出和索引相關,並且和索引讀取相關。那麼爲何長主鍵在索引中會影響性能?性能

上面是 Innodb 的索引數據結構。左邊是聚簇索引,經過主鍵定位數據記錄。右邊是二級索引,對列數據作索引,經過列數據查找數據主鍵。若是經過二級索引查詢數據,流程如圖上所示,先從二級索引樹上搜索到主鍵,而後在聚簇索引上經過主鍵搜索到數據行。其中二級索引的葉子節點是直接存儲的主鍵值,而不是主鍵指針。因此若是主鍵太長,一個二級索引樹所能存儲的索引記錄就會變少,這樣在有限的索引緩衝中,須要讀取磁盤的次數就會變多,因此性能就會降低。

3、爲何建議使用自增 ID

InnoDB 使用聚簇索引,如上圖所示,數據記錄自己被存於主索引(一顆 B+Tree)的葉子節點上。這就要求同一個葉子節點內(大小爲一個內存頁或磁盤頁)的各條數據記錄按主鍵順序存放,所以每當有一條新的記錄插入時,MySQL 會根據其主鍵將其插入適當的節點和位置,若是頁面達到裝載因子(InnoDB 默認爲 15/16),則開闢一個新的頁(節點)。

若是表使用自增主鍵,那麼每次插入新的記錄,記錄就會順序添加到當前索引節點的後續位置,當一頁寫滿,就會自動開闢一個新的頁。這樣就會造成一個緊湊的索引結構,近似順序填滿。因爲每次插入時也不須要移動已有數據,所以效率很高,也不會增長不少開銷在維護索引上,以下圖左側所示。不然因爲每次插入主鍵的值近似於隨機,所以每次新記錄都要被插到現有索引頁的中間某個位置,MySQL 不得不爲了將新記錄插到合適位置而移動數據,以下圖右側所示,這樣就形成了必定的開銷。因爲此,Mysql 爲維護索引可能須要頻繁的刷新緩衝,增長了方法磁盤 IO 的次數,並且時常須要對索引結構進行重組織。

4、業務 Key VS 邏輯 Key

業務 Key,即便用具備業務意義的 id 做爲 Key,好比使用訂單流水號做爲訂單表的主鍵 Key。邏輯 Key,即無關業務的 Key,按某種規則生成 Key,如自增 Key。

業務 Key 的優勢

  • Key 具備業務意義,在查詢時能夠直接做爲搜索關鍵字使用
  • 不須要額外的列和索引空間
  • 能夠減小一些 join 操做。

業務 Key 的缺點

  • 當業務發生變化時,有時須要變動主鍵
  • 涉及多列 Key 時比較難操做
  • 業務 Key 每每比較長,所佔空間更大,致使更大的磁盤 IO
  • 在 Key 肯定前不能持久化數據,有時咱們沒有在肯定數據 Key 時,就想先添加一條記錄,以後再更新業務 Key
  • 設計一個兼具易用和性能的 Key 生成方案比較難

邏輯 Key 的優勢

  • 不會由於業務的變更而須要修改 Key 邏輯
  • 操做簡單,且易於管理
  • 邏輯 Key 每每更小,性能更優
  • 邏輯 Key 更容易保證惟一性
  • 更易於優化

邏輯 Key 缺點

  • 查詢主鍵列和主鍵索引須要額外的磁盤空間
  • 在插入數據和更新數據時須要額外的 IO
  • 更多的 join 可能
  • 若是沒有惟一性策略限制,容易出現重複的 Key
  • 測試環境和正式環境 Key 不一致,不利於排查問題
  • Key 的值沒有和數據關聯,不符合三範式
  • 不能用於搜索關鍵字
  • 依賴不一樣數據庫系統的具體實現,不利於底層數據庫的替換

5、主鍵生成

通常狀況下,咱們都使用 Mysql 的自增 ID,來做爲表的主鍵,這樣簡單,並且從上面講到的來看,性能也是最好的。可是在分庫分表的狀況狀況下,自增 ID 則不能知足需求。咱們能夠來看看不一樣數據庫生成 ID 的方式,也看一些分佈式 ID 生成方案。利於咱們思考甚至實現本身的分佈式 ID 生成服務。

數據庫的實現

Mysql 自增

Mysql 在內存中維護一個自增計數器,每次訪問 auto-increment 計數器的時候, InnoDB 都會加上一個名爲AUTO-INC 鎖直到該語句結束(注意鎖只持有到語句結束,不是事務結束)。AUTO-INC 鎖是一個特殊的表級別的鎖,用來提高包含 auto_increment 列的併發插入性。

在分佈式的狀況下,其實能夠獨立一個服務和數據庫來作 id 生成,依舊依賴 Mysql 的表 id 自增能力來爲第三方服務統一輩子成 id。爲性能考慮能夠不一樣業務使用不一樣的表。

Mongodb ObjectId

Mongodb 爲防止主鍵衝突,設計了一個 ObjectId 做爲主鍵 id。它由一個 12 字節的十六進制數字組成,其中包含如下幾部分:

  1. Time:時間戳。4 字節。秒級。

  2. Machine:機器標識。3 字節。通常是機器主機名的散列值,這樣就確保了不一樣主機生成不一樣的機器 hash 值,確保在分佈式中不形成衝突,同一臺機器的值相同。

  3. PID:進程 ID。2 字節。上面的 Machine 是爲了確保在不一樣機器產生的 objectId 不衝突,而 pid 就是爲了在同一臺機器不一樣的 mongodb 進程產生的 objectId 不衝突。

  4. INC:自增計數器。3 字節。前面的九個字節保證了一秒內不一樣機器不一樣進程生成的 objectId 不衝突,自增計數器,用來確保在同一秒內產生的 objectId 也不會發現衝突,容許 256 的 3 次方等於 16777216 條記錄的惟一性。

Cassandra TimeUUID

Cassandra 使用下面規則生成一個惟一的 id:time + MAC + sequence

方案

  1. Zookeeper 自增:經過 zk 的自增機制實現。
  2. Redis 自增:經過 Redis 的自增機制實現。
  3. UUID:使用 UUID 字符串做爲 Key。
  4. snowflake 算法:和 Mongodb 的實現相似,1位符號位 + 41位時間戳(毫秒級)+ 10位數據機器位 + 12位毫秒內的序列

開源實現

  1. 百度 UidGenerator:基於snowflake算法。
  2. 美團 Leaf:同時實現了基於 Mysql 自增(優化)和 snowflake 算法的機制。

推薦系列

列式存儲
時間序列數據庫(TSDB)初識與選擇
十分鐘瞭解 Apache Druid
Apache Druid 底層存儲設計
Apache Druid 的集羣設計與工做流程
Mysql 大表問題和解決

想了解更多數據存儲相關知識,請關注個人公衆號。

MageByte

相關文章
相關標籤/搜索