MySQL只學有用的--你知道自增ID會用完嗎?

MySQL裏的自增ID是定義了初始值,然後不停地加步長。我們在創建這個字段的時候會給指定一個字節長度。這個字節長度就是這個ID的上限。比如:無符號整型(unsigned int)是4個字節,上限就是 232=12^{32}=1

既然有上限,那麼就有可能用完? 下面我們就來聊一聊自增ID用完了怎麼辦?

下面我們會通過幾種不同的自增ID,來分析一下它們的值達到上限以後的情況。

表定義自增值 ID

表定義的自增是比較常用的一種方式,通過就是一張表裏面的ID主鍵,設置成一個自增長。
表定義的自增值達到上限後的邏輯是:再申請下一個ID時,得到的值保持不變。
我們可以通過下面這個語句序列驗證:
請依次執行下面的語句,不要一次性都執行,這樣你可以觀察一下結果。

# create table t_test(id int unsigned auto_increment primary key ) auto_increment = 4294967295;

# insert into t_test VALUES(null);

# show create table t_test;

insert into t_test VALUES(null);

下面我們來解釋一下這些SQL
第一行,表示創建一個表,id爲主鍵int類型,unsigned代表無符合只能是正數,
auto_increment 代表爲自增, primary key表示主鍵, auto_increment =num 表示步長爲num。
第二行插入一條記錄,爲null是希望使用數據庫自動生成的參數。
第三行查看錶的結構。
第四條在次使用mysql 自動生成的參數插入一條數據。

最後的運行結果爲

insert into t_test VALUES(null)
> 1062 - Duplicate entry '4294967295' for key 'PRIMARY'
> 時間: 0.022s

原因是第一個insert 語句插入數據成功後,這個表的AUTO_INCREMENT 沒有改變(還是4294967295), 就導致了第二個insert 語句又拿到相同的自增ID值,再試圖執行插入語句,報主鍵衝突錯誤。

如果是一個頻繁插入刪除數據的表,可以選擇使用8個字節的bigint unsigned.

話外題: 爲什麼默認最大值是4294967295? 爲什麼要有unsigned? 爲什麼不是設置int(11)呢?

先回答第一個問題,最大值怎麼計算,一個字節代表8位,計算機也是二進制語言,一位代表兩個數,因此公式爲 248=12^{4*8}=1

第二個問題,爲什麼要有unsigned?
因爲我們mysql中的數字類型是有正負數的,而ID都是從零開始的,如果在有負數的情況下, int類型4個字節,能生成的ID數量只有4294967295除以2。整整少了一半,因此就需要設置上去掉負數的類型。

第三個問題: 爲什麼不是設置int(11)呢?
在SQL語句中int代表你要創建字段的類型,int代表整型,11代表字段的長度。

這個11代表顯示寬度,整數列的顯示寬度與mysql需要用多少個字符來顯示該列數值,與該整數需要的存儲空間的大小都沒有關係,比如,不管設定了顯示寬度是多少個字符,bigint都要佔用8個字節。

int是整型,(11)是指顯示字符的長度,但要加參數的,最大爲255,比如它是記錄行數的id,插入10筆資料,它就顯示00000000001 ~~~00000000010,當字符的位數超過11,它也只顯示11位,如果你沒有加那個讓它未滿11位就前面加0的參數,它不會在前面加0

聲明整型數據列時,我們可以爲它指定個顯示寬度M(1~255),如INT(5),指定顯示寬度爲5個字符,如果沒有給它指定顯示寬度,MySQL會爲它指定一個默認值。顯示寬度只用於顯示,並不能限制取值範圍和佔用空間,如:INT(3)會佔用4個字節的存儲空間,並且允許的最大值也不會是999,而是 INT整型所允許的最大值。

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能寫到數據表中的值,就有兩上特徵:

  1. row_id寫入表中的值範圍,是從0到2481 2^{48}-1
  2. 當dict_sys.row_id 到達上限時,如果再有插入數據的行爲再來申請row_id,拿到以後再取最後6個字節的話就是0,取到0以後,再有插入數據的行爲就會是1。這樣就開始了新的一輪從0到最大值2481 2^{48}-1

這樣會有一個後果,當開始新的一輪獲取row_id之後,會將原來相同row_id的數據覆蓋。

下面我們來一起驗證一下:

  1. 我們創建一張表test_inc,不包含任何索引。
create table test_inc(id int) engine=innodb;
  1. 通過ps -ef|grep mysql得到對應的進程號,使用gdb來開始做下調試配置,切記!此處應該是自己的測試環境。
gdb -p 3132 -ex 'p dict_sys->row_id=1' -batch  
  1. 插入一些數據,使得rowid持續自增。
 insert into test_inc values(1),(2),(3);  
  1. 我們對rowid進行重置,調整爲2^48
 gdb -p 3132 -ex 'p dict_sys->row_id=281474976710656' -batch  
  1. 繼續寫入一些數據,比如我們寫入4,5,6三行數據
 insert into test_inc values(4),(5),(6);  
  1. 查看數據結果,發現1,2兩行已經被覆蓋了。
select *from test_inc;  
+------+ 
| id |  
+------+  
| 4 |  
| 5 |  
| 6 |  
| 3 |  
+------+  
4 rows in set (0.00 sec) 

從這個角度看,我們還是應該在InnoDB表中主動創建自增主鍵。因爲相對於報主鍵衝突錯誤,我們無法接受數據丟失。
覆蓋數據影響的是數據的可靠性,主鍵衝突,是插入失敗影響的是可用性。而一般情況下,可靠性優先於可用性。

Xid,trx_id,tread_id

XID只需要不在同一個binlog文件中出現重複值即可。雖然理論上會出現重複值,但是概率極小,忽略不計。
trx_id 要想出現重複值會出現一個B需要在每秒50萬的訪問量下,連續跑17.8年。

業務ID選擇

業務ID選擇要分具體的業務場景,如果是小的傳統項目,數據量不大的話,直接使用表自增就可以了。 如果int不夠的話,設置成bigint就夠了。

如果是像視頻網站中 觀看記錄表,因爲是長年累月的增加,建議使用分佈式的ID生成器。 也方便以後分庫分表。

這裏不太推薦雪花算法, 因爲他本身的長度太長了。對索引的使用不是很友好。

在這裏推薦一個ID生成器,是我一個朋友寫的。我也貢獻了一點點代碼。

https://github.com/barleyawn/zebra

借鑑

極客時間<MySQL實戰45講>
MySQL中int(11)最大長度是多少?
自增row_id驗證

交個朋友好嗎?

以上內容均爲讀書所得, 更多有趣有料的科技資訊請關注公衆號。(交個朋友)
在這裏插入圖片描述

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