MySQL的自增 ID 用完了,怎么辦?


如果你用過或了解過MySQL,那你一定知道自增主鍵了。每個自增id都是定義了初始值,然后按照指定步長增長(默認步長是1)。

雖然,自然數是沒有上限的,但是我們在設計表結構的時候,通常都會指定字段長度,那么,這時候id就有上限了。

既然有上限,就總有被用完的時候,如果id用完了,怎么辦呢?今天就一起來學習下吧。

自增id

說到自增id,相信你的第一反應一定是在設計表結構的時候自定義一個自增id字段,那么就有一個問題啦,

在插入數據時有可能唯一主鍵沖、sql事務回滾、批量插入的時候,批量申請自增值等原因導致自增id是不連續的。

表定義的自增值達到上線后的邏輯是:再申請下一個id的時候,獲取的是同一個值(最大值)。

大家可以插入sql設置id是最大值,再insert一條不主動設置id的語句就可以驗證這一結論啦。這個時候如果再插入就是報主鍵沖突咯~

這里提醒一下:232-1(4294967295)不是一個特別大的數,對於一個頻繁插入刪除數據的表來說,是可能會被用完的。

因此在建表的時候你需要考察你的表是否有可能達到這個上限,如果有可能,就應該創建成 8 個字節的 bigint unsigned。

InnoDB系統自增row_id

如果你創建的 InnoDB 表沒有指定主鍵,那么 InnoDB 會給你創建一個不可見的,長度為 6 個字節的 row_id。

InnoDB 維護了一個全局的 dict_sys.row_id 值,所有無主鍵的 InnoDB 表,每插入一行數據,

都將當前的 dict_sys.row_id 值作為要插入數據的 row_id,然后把 dict_sys.row_id 的值加 1。

實際上,在代碼實現時 row_id 是一個長度為8字節的無符號長整型 (bigint unsigned)。

但是,InnoDB 在設計時,給 row_id 留的只是 6 個字節的長度,這樣寫到數據表中時只放了最后 6 個字節,所以 row_id 能寫到數據表中的值,

就有兩個特征:

row_id 寫入表中的值范圍,是從 0 到 248-1;
當 dict_sys.row_id=2^48時,如果再有插入數據的行為要來申請 row_id,拿到以后再取最后 6 個字節的話就是 0。

雖然,2^48這個數字已經很大了,但是大家要知道 一個系統是可以跑很久的,那么還是可能達到上限的,

這時候再申請就會覆蓋原來的記錄了。因此,盡量不要選擇這種方式!

Xid

MySQL中redo log 和 binlog 相配合的時候,它們有一個共同的字段叫作 Xid。它在 MySQL 中是用來對應事務的。

MySQL 內部維護了一個全局變量 global_query_id,每次執行語句的時候將它賦值給 Query_id,然后給這個變量加 1。

如果當前語句是這個事務執行的第一條語句,那么 MySQL 還會同時把 Query_id 賦值給這個事務的 Xid。而 global_query_id 是一個純內存變量,重啟之后就清零了。

所以在同一個數據庫實例中,不同事務的 Xid 也是有可能相同的。

Innodb trx_id

InnoDB 內部維護了一個 max_trx_id 全局變量,每次需要申請一個新的 trx_id 時,就獲得 max_trx_id 的當前值,然后並將 max_trx_id 加 1。

InnoDB 數據可見性的核心思想是:每一行數據都記錄了更新它的 trx_id,當一個事務讀到一行數據的時候,判斷這個數據是否可見的方法,

就是通過事務的一致性視圖與這行數據的 trx_id 做對比。但是這個過程有臟讀存在,那么這個id就不會是原子性的,存在重復的可能性。

thread_id

其實,線程 id 才是 MySQL 中最常見的一種自增 id。平時我們在查各種現場的時候,show processlist 里面的第一列,就是 thread_id。

thread_id 的邏輯很好理解:系統保存了一個全局變量 thread_id_counter,每新建一個連接,就將 thread_id_counter 賦值給這個新連接的線程變量。

thread_id_counter 定義的大小是 4 個字節,因此達到 232-1 后,它就會重置為 0,然后繼續增加。結果跟row_id一樣,就會覆蓋原有記錄了。

上面介紹了幾種MySQL自身的一些自增id,其實,實際運用中,我們也可能會選擇外部的自增主鍵,然后持久化到數據庫,以此來代替數據庫自身的自增id。下面來說說吧。

Redis自增主鍵

其實外部自增主鍵的生成方式有很多,為什么我要介紹redis呢?因為我自己在實際應用中使用發現它的很多優點。

redis自身是原子性的,因此高並發也是線程安全的。假設主鍵字段長度20,我們以時間+自增數來構成主鍵,

例如:8位日期+12自增數。那么,根據業務性質可以決定時間取年月日或者到毫秒級,那么在毫秒之間自增數的重復概率是極小極小的,基本的業務都能適用。

總結

上面介紹了好幾種自增id,每種自增 id 有各自的應用場景,在達到上限后的表現也不同:

1、 表的自增 id 達到上限后,再申請時它的值就不會改變,進而導致繼續插入數據時報主鍵沖突的錯誤
2、 row_id 達到上限后,則會歸 0 再重新遞增,如果出現相同的 row_id,后寫的數據會覆蓋之前的數據
3、 Xid 只需要不在同一個 binlog 文件中出現重復值即可。雖然理論上會出現重復值,但是概率極小,可以忽略不計
4、 InnoDB 的 max_trx_id 遞增值每次 MySQL 重啟都會被保存起來,所以我們文章中提到的臟讀的例子就是一個必現的 bug,好在留給我們的時間還很充裕
5、 thread_id 是我們使用中最常見的,而且也是處理得最好的一個自增 id 邏輯了
6、 redis外部自增,毫秒級別,理論上會出現重復值,但是概率極小,可以忽略不計
7、 其實,每種自增id都有各自的適用場景,大家在平時使用中可以根據具體場景再選擇。

但是要未雨綢繆,因為系統的運行時間和數據的存儲,這些都是要考慮在內的,綜合考慮,選擇一個在系統運行期間一定不會出現重復即刻。


免責聲明!

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



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