學習筆記 - MySQL自增主鍵值

2020-10-03 14:00:19

1、有朋友給我留言

為什麼他的庫表裡面,有些是連續的,有些不是連續的,而且他的表都是自增(`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主鍵id'),他的潛意識裡這個id應該是連續的才對。因此準備梳理梳理mysql自增主鍵的值的變化情況,讓朋友有個比較全面的瞭解。

 

2、自增值儲存說明

2.1  MyISAM 引擎的自增值儲存在資料檔案中。

2.2  InnoDB 引擎的自增值,其實是儲存在了記憶體裡,並且到了 MySQL 8.0 版本後,才有了「自增值持久化」的能力,也就是才實現了「如果發生重新啟動,表的自增值可以恢復為 MySQL 重新啟動前的值」,具體情況是:

a)在 MySQL 5.7 及之前的版本,自增值儲存在記憶體裡。每次重新啟動後,第一次開啟表的時候,都會去找自增值的最大值 max(id),然後將 max(id) + 1 作為這個表當前的自增值。
b)在 MySQL 8.0 版本,將自增值的變更記錄在了 redo log 中,重新啟動的時候依靠 redo log 恢復重新啟動之前的值。

 

3、自增值修改機制

在 MySQL 裡面,如果欄位 id 被定義為 AUTO_INCREMENT,在插入一行資料的時候,自增值的行為如下:

如果插入資料時 id 欄位指定為 0、null 或未指定值,那麼就把這個表當前的 AUTO_INCREMENT 值填到自增欄位;
如果插入資料時 id 欄位指定了具體的值,就直接使用語句裡指定的值。
     根據要插入的值和當前自增值的大小關係,自增值的變更結果也會有所不同。假設,某次要插入的值是 i,當前的自增值是 j。

如果 i<j,那麼這個表的自增值不變;
如果 i≥j,就需要把當前自增值修改為新的自增值。
     新的自增值生成演演算法是:從 auto_increment_offset 開始,以 auto_increment_increment 為步長,持續疊加,直到找到第一個大於i的值,作為新的自增值。其中,auto_increment_offset 和 auto_increment_increment 是兩個系統引數,分別用來表示自增的初始值和步長,預設值都是 1。

 

4、 導致自增值不連續的原因

4.1 唯一鍵衝突
       假設執行 SQL 的時候 user 表 id = 10,此時在記憶體中的自增 id 為11,此時發生唯一鍵衝突寫庫失敗,則 user 表沒有 id = 10 這條記錄,之後 id 從11開始寫入,因此 id 是不連續的。

4.2 事務回滾
       innodb設計的時候語句執行失敗也不回退自增id,也正是因為這樣,所有才只保證了自增id是遞增的,但不保證是連續的。假設同時需要對 user、staff 表進行寫庫操作,執行 SQL 的時候 user 表 id = 10,此時在記憶體中的自增 id 為11;staff 表 id = 20,此時記憶體中的自增 id 為21,一旦事務執行失敗,事務回滾,寫庫失敗,則 user 表沒有 id = 10 這條記錄,staff 表沒有 id = 20 這條記錄,user 表從11開始寫入,staff 表從21開始寫入,如此產生 id 不連續的現象。

4.3 批次寫庫操作
       對於批次插入資料的語句,MySQL 有一個批次申請自增 id 的策略:

       a). 語句執行過程中,第一次申請自增 id,會分配 1 個;

       b). 1 個用完以後,這個語句第二次申請自增 id,會分配 2 個;

       c). 2 個用完以後,還是這個語句,第三次申請自增 id,會分配 4 個;

       依此類推,同一個語句去申請自增 id,每次申請到的自增 id 個數都是上一次的兩倍。

       假設批次往 user 表中寫入四條記錄,則這四條記錄將分為三次申請id,

       第一次分配到 id = 1,第二次分配到 id = 2、3 ,第三次分配到 id = 4、5、6、7,當批次寫入四條記錄之後,id = 1、2、3、4將會入庫,但是 id = 5、6、7就被廢棄了,下一個 id 從8開始。

 

4.4 大量delete操作,物理刪除資料,delete from tbname where xxxx;這樣硬刪除會導致id不連續。

 

5、如何實現自增id值遞增且連續

如果想讓自己的bigint型別主鍵id欄位值是連續的,要怎麼實現呢,MySQL底層預設是實現不了的,需要我們自己做下特殊處理。

5.1、做成觸發器的方式

insert:每次進行insert插入操作時,都先執行查詢操作,得到該序列號,然後插入時再加1;當然這種辦法是最笨的方法,而且需要多執行一次操作

update: update的時候,如果update主鍵值,要判斷主鍵值是否是max(id)+1,如果不是設定成update(id)+1.

delete:不用做任何操作,甚至穩妥起見,不能物理delete,只能設定一個status=-1這樣的邏輯刪除。

 

5.2、業務層的處理

在每次插入資料之後,修正那個AUTO_INCREMENT值。就是   : 先 執行 ALTER TABLE `table` AUTO_INCREMENT =1

執行之後,在執行插入操作,這樣就可以讓自增型別連續自增;  (注:不一定再插入的id就是1;而是表中id最大值+1。),而且不允許硬刪除delete操作。