自增還是UUID?數據庫主鍵的類型選擇,為啥不能用uuid做MySQL的主鍵?


一、自增還是UUID?數據庫主鍵的類型選擇

  自增還是UUID?這個問題看似簡單,但是能誘發很多思考,也涉及到了很多細節。先說下uuid和 auto_increment(數據庫自增主鍵)的優缺點吧,因為是個人理解,如有錯誤懇請指出:

1、自增主鍵

  自增ID是在設計表時將id字段的值設置為自增的形式,這樣當插入一行數據時無需指定id會自動根據前一字段的ID值+1進行填充。在MySQL數據庫中,可通過sql語句AUTO_INCREMENT來對特定的字段啟用自增賦值 使用自增ID作為主鍵,能夠保證字段的原子性。

  auto_increment優點:

  • 字段長度較uuid小很多,可以是bigint甚至是int類型,這對檢索的性能會有所影響。我們平時數據庫一般用的都是innodb引擎的表,這種表格檢索數據的時候,哪怕走索引,也是先根據索引找到主鍵,然后由主鍵找到這條記錄。所以主鍵的長度短的話,讀性能是會好一點的。
  • 在寫的方面,因為是自增的,所以主鍵是趨勢自增的,也就是說新增的數據永遠在后面,這點對於性能有很大的提升(這點我接下來會在uuid的優缺點分析中解釋,雖然用詞可能不太專業)
  • 數據庫自動編號,速度快,而且是增量增長,按順序存放,對於檢索非常有利;
  • 數字型,占用空間小,易排序,在程序中傳遞也方便;
  • 如果通過非系統增加記錄時,可以不用指定該字段,不用擔心主鍵重復問題。

  auto_incremen的缺點:

  • 最致命的一個缺點就是,很容易被別人知曉業務量,然后很容易被網絡爬蟲教做人
  • 高並發的情況下,競爭自增鎖會降低數據庫的吞吐能力
  • 數據遷移的時候,特別是發生表格合並這種操作的時候,會非常蛋疼

  因為自動增長,在手動要插入指定ID的記錄時會顯得麻煩,尤其是當系統與其它系統集成時,需要數據導入時,很難保證原系統的ID不發生主鍵沖突(前提是老系統也是數字型的)。特別是在新系統上線時,新舊系統並行存在,並且是異庫異構的數據庫的情況下,需要雙向同步時,自增主鍵將是你的噩夢;在系統集成或割接時,如果新舊系統主鍵不同是數字型就會導致修改主鍵數據類型,這也會導致其它有外鍵關聯的表的修改,后果同樣很嚴重;若系統也是數字型的,在導入時,為了區分新老數據,可能想在老數據主鍵前統一加一個字符標識(例如“o”,old)來表示這是老數據,那么自動增長的數字型又面臨一個挑戰。

2、UUID

  UUID含義是通用唯一識別碼 (Universally Unique Identifier),指在一台機器上生成的數字,它保證對在同一時空中的所有機器都是唯一的。通常平台會提供生成的API。換句話說能夠在一定的范圍內保證主鍵id的唯一性。

  優點:

  • 地球唯一的guid,絕對不會沖突。數據拆分、合並存儲的時候,能達到全局的唯一性
  • 可以在應用層生成,提高數據庫吞吐能力
  • 是string類型,寫代碼的時候方便很多

  缺點:

  • 影響插入速度, 並且造成硬盤使用率低。與自增相比,最大的缺陷就是隨機io。這一點又要談到我們的innodb了,因為這個默認引擎,表中數據是按照主鍵順序存放的。也就是說,如果發生了隨機io,那么就會頻繁地移動磁盤塊。當數據量大的時候,寫的短板將非常明顯。當然,這個缺點可以通過nosql那些產品解決。
  • uuid之間比較大小相對數字慢不少, 影響查詢速度。
  • uuid占空間大, 如果你建的索引越多, 影響越嚴重
  • 讀取出來的數據也是沒有規律的,通常需要order by,其實也很消耗數據庫資源
  • 看起來比較丑

二、為啥不能用uuid做MySQL的主鍵?

  在 MySQL 中設計表的時候,MySQL 官方推薦不要使用 uuid 或者不連續不重復的雪花 id(long 形且唯一,單機遞增),而是推薦連續自增的主鍵 id,官方的推薦是 auto_increment。

  那么為什么不建議采用 uuid,使用 uuid 究竟有什么壞處?要說明這個問題,我們首先來建立三張表,分別是:user_auto_key、user_uuid、user_random_key,他們分別表示自動增長的主鍵,uuid 作為主鍵,隨機 key 作為主鍵,其他我們完全保持不變。

  根據控制變量法,我們只把每個表的主鍵使用不同的策略生成,而其他的字段完全一樣,然后測試一下表的插入速度和查詢速度。

  注:這里的隨機 key 其實是指用雪花算法算出來的前后不連續不重復無規律的id:一串 18 位長度的 long 值。

  光有理論不行,直接上程序,使用 Spring 的 jdbcTemplate 來實現增查測試。

  技術框架:Spring Boot+jdbcTemplate+junit+hutool,程序的原理就是連接自己的測試數據庫,然后在相同的環境下寫入同等數量的數據,來分析一下 insert 插入的時間來進行綜合其效率。

  為了做到最真實的效果,所有的數據采用隨機生成,比如名字、郵箱、地址都是隨機生成:

  程序寫入效率測試結果

  在已有數據量為 130W 的時候:我們再來測試一下插入 10w 數據,看看會有什么結果:

  可以看出在數據量 100W 左右的時候,uuid 的插入效率墊底,並且在后序增加了 130W 的數據,uuid 的時間又直線下降。

  時間占用量總體可以打出的效率排名為:auto_key>random_key>uuid。

  uuid 的效率最低,在數據量較大的情況下,效率直線下滑。那么為什么會出現這樣的現象呢?帶着疑問,我們來探討一下這個問題:

三、使用 uuid 和自增 id 的索引結構對比

1、使用自增 id 的內部結構

  自增的主鍵的值是順序的,所以 InnoDB 把每一條記錄都存儲在一條記錄的后面。

  當達到頁面的最大填充因子時候(InnoDB 默認的最大填充因子是頁大小的 15/16,會留出 1/16 的空間留作以后的修改)。

  下一條記錄就會寫入新的頁中,一旦數據按照這種順序的方式加載,主鍵頁就會近乎於順序的記錄填滿,提升了頁面的最大填充率,不會有頁的浪費。

  新插入的行一定會在原有的最大數據行下一行,MySQL 定位和尋址很快,不會為計算新行的位置而做出額外的消耗。

  減少了頁分裂和碎片的產生。

2、使用 uuid 的索引內部結構

  因為 uuid 相對順序的自增 id 來說是毫無規律可言的,新行的值不一定要比之前的主鍵的值要大,所以 innodb 無法做到總是把新行插入到索引的最后,而是需要為新行尋找新的合適的位置從而來分配新的空間。

  這個過程需要做很多額外的操作,數據的毫無順序會導致數據分布散亂,將會導致以下的問題:

  寫入的目標頁很可能已經刷新到磁盤上並且從緩存上移除,或者還沒有被加載到緩存中,innodb 在插入之前不得不先找到並從磁盤讀取目標頁到內存中,這將導致大量的隨機 IO。

  因為寫入是亂序的,innodb 不得不頻繁的做頁分裂操作,以便為新的行分配空間,頁分裂導致移動大量的數據,一次插入最少需要修改三個頁以上。

  由於頻繁的頁分裂,頁會變得稀疏並被不規則的填充,最終會導致數據會有碎片。在把隨機值(uuid 和雪花 id)載入到聚簇索引(InnoDB 默認的索引類型)以后,有時候會需要做一次 OPTIMEIZE TABLE 來重建表並優化頁的填充,這將又需要一定的時間消耗。

  結論:使用 InnoDB 應該盡可能的按主鍵的自增順序插入,並且盡可能使用單調的增加的聚簇鍵的值來插入新行。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM