MySQL 自增主鍵

以下僅考慮 InnoDB 存儲引擎。

自增主鍵有兩個性質需要考慮:

  • 單調性
    每次插入一條數據,其 ID 都是比上一條插入的數據的 ID 大,就算上一條數據被刪除。
  • 連續性
    插入成功時,其數據的 ID 和前一次插入成功時數據的 ID 相鄰。

自增主鍵的單調性

爲何會有單調性的問題?

這主要跟自增主鍵最大值的獲取方式,以及存放位置有關係。

如果最大值是通過計算獲取的,並且在某些情況下需要重新獲取時,會因爲最新的數據被刪除而減小。

自增主鍵最大值怎麼取的?存放到哪裏?

MySQL 5.7 及之前的版本,自增主鍵最大值會在啓動(重啓)後從數據庫中取出放到內存:

SELECT MAX(ai_col) FROM table_name FOR UPDATE; 

這樣獲取是通過計算的,並且由於存放在內存而容易丟失。

如果刪除最新一條數據(假設 ID 爲 10),因故障或者其他必要重啓後再插入一條數據時會使用之前的 ID (即 ID 爲 10)。

問題在於如果有其他表依賴了該 ID,則其他表的數據關聯到的數據就符合要求了。除非設置了外鍵。

比如我要向最大一個 ID 的賬號充了 100 萬。但是在充值之前,該賬號被刪除,然後服務器故障重啓,重啓後有人新註冊了一個賬號。結果我的 100 萬充到了他的新賬號上。註冊新賬號的人以爲是新手福利,笑嘻嘻。

如何解決單調性的問題?

從 MySQL 8.0 開始,自增主鍵最大值會在每次修改後寫入到 redo log,並且在每個檢查點寫入引擎私有的系統表。

  • 如果是正常重啓,則讀取系統表裏的值。
  • 如果是故障重啓,則先讀取系統表裏的值放到內存。接着掃描 redo log 裏存儲的值。如果掃描到的值大於內存的值,則將該值覆蓋到內存。

但由於數據庫可能在 redo log 刷入磁盤前就故障了,所以可能會用到之前申請的 ID。

注:如果 redo log 都沒刷入,就更不用說將數據插入數據表了。

InnoDB AUTO_INCREMENT Counter Initialization
https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html#innodb-auto-increment-initialization

自增主鍵插入時的連續性

這裏不考慮由於刪除導致的連續性問題

爲何會有連續性問題?

這主要是跟插入事務回滾有關係。

對於兩個插入事務,事務 A 先執行插入語句,之後事務 B 執行插入語句。在這之後,事務 A 回滾,導致 A 執行插入語句時佔用的 ID 被拋棄。

之所以事務 A 沒提交的情況下,事務 B 就能執行插入語句,跟 InnoDB 的自增長鎖(AUTO-INC Locking)相關。該鎖是一種特殊的表鎖(table-level lock),但會在插入語句執行後立即釋放,不會等到事務結束。

如何解決連續性問題?

使用最高隔離級別 SERIALIZABLE (串行)。

由於性能上的考慮,通常不這樣做。

多事務批量插入的連續性

事務 A 和事務 B 都在執行 不確定數量 的批量插入(INSERT ... SELECT):

  • 保證事務 A 的數據的 ID 連續: innodb_autoinc_lock_mode = 0 (AUTO-INC Locking)
    必須等待語句執行結束才釋放鎖。
  • 保證事務 A 的數據的 ID 連續: innodb_autoinc_lock_mode = 1 (AUTO-INC Locking)
    和上面的區別在於,當執行 確定數量 的批量插入時,使用輕量級互斥量(mutex)而不是特殊表鎖(AUTO-INC Locking),從而提前向內存的計數器申請相應數量的 ID。之後立即釋放,不用等語句執行結束。
    會因爲回滾而使得全局 ID 不連續。
  • 不保證事務 A 的數據的 ID 連續: innodb_autoinc_lock_mode = 2 (mutex)

三種插入定義:

  • 簡單插入
    能夠提前知道插入的行數
  • 批量插入
    不能提前知道插入的行數
  • 混合插入
    批量插入中的一部分的 ID 是指定的(非 0 且非 NULL),另一部分未指定,使用數據庫生成的自增 ID。

其他

如果主動指定 ID 爲 0 或者 NULL 插入,則會使用數據庫生成的自增 ID。

參考文檔

爲什麼 MySQL 的自增主鍵不單調也不連續
https://database.51cto.com/art/202004/614923.htm
《MySQL技術內幕——InnoDB存儲引擎》 第 6 章:鎖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章