01 前言
數(shù)據(jù)庫中的自增 ID 用完了該怎么辦埠况?
這個問題其實可以分為有主鍵 & 無主鍵兩種情況回答耸携。
先上張腦圖:
02 有主鍵
如果你的表有主鍵,并且把主鍵設置為自增辕翰。
在 MySQL 中夺衍,一般會把主鍵設置成 int 型。而 MySQL 中 int 型占用 4 個字節(jié)喜命,作為有符號位的話范圍就是 [-231,231-1]沟沙,也就是[-2147483648,2147483647];無符號位的話最大值就是 2^32-1壁榕,也就是 4294967295矛紫。
下面以有符號位創(chuàng)建一張表:
CREATE TABLE IF NOT EXISTS `t`(
`id` INT(11) NOT NULL AUTO_INCREMENT,
`url` VARCHAR(64) NOT NULL,
PRIMARY KEY ( `id` )
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
復制代碼
插入一個 id 為最大值 2147483647 的值,如下圖所示:
如果此時繼續(xù)下面的插入語句:
INSERT INTO t (url) VALUES ('wwww.javafish.top/article/erwt/spring')
復制代碼
結果就會造成主鍵沖突:
2.1 解決方案
雖說 int 4 個字節(jié)牌里,最大數(shù)據(jù)量能存儲 21 億颊咬。你可能會覺得這么大的容量,應該不至于用完二庵。但是互聯(lián)網時代贪染,每天都產生大量的數(shù)據(jù),這是很有可能達到的催享。
所以杭隙,我們的解決方案是:把主鍵類型改為 bigint,也就是 8 個字節(jié)因妙。這樣能存儲的最大數(shù)據(jù)量就是 2^64-1痰憎,我也數(shù)不清有多少了。反正在你有生之年應該是夠用的攀涵。
PS:單表 21 億的數(shù)據(jù)量顯然不現(xiàn)實铣耘,一般來說數(shù)據(jù)量達到 500 萬就該分表了。
03 沒主鍵
另一種情況就是建表時沒設置主鍵以故。這種情況蜗细,InnoDB 會自動幫你創(chuàng)建一個不可見的、長度為 6 字節(jié)的 row_id,默認是無符號的炉媒,所以最大長度是 2^48-1踪区。
實際上 InnoDB 維護了一個全局的 dictsys.row_id,所以未定義主鍵的表都共享該 row_id吊骤,并不是單表獨享缎岗。每次插入一條數(shù)據(jù),都把全局 row_id 當成主鍵 id白粉,然后全局 row_id 加 1传泊。
這種情況的數(shù)據(jù)庫自增 ID 用完會發(fā)生什么呢?
1鸭巴、創(chuàng)建一張無顯示設置主鍵的表 t:
CREATE TABLE IF NOT EXISTS `t`(
`age` int(4) NOT NULL
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
復制代碼
2眷细、通過 ps -ef|grep mysql
命令獲取 mysql 的進程 ID,然后執(zhí)行命令奕扣,通過 gdb 先把 row_id 修改為 1薪鹦。PS:沒有 gdb 的,百度安裝下
sudo gdb -p 16111 -ex 'p dict_sys->row_id=1' -batch
復制代碼
出現(xiàn)下圖就是沒錯的:
3惯豆、插入三條數(shù)據(jù):
insert into t(age) values(1);
insert into t(age) values(2);
insert into t(age) values(3);
此時的數(shù)據(jù)庫數(shù)據(jù):
4池磁、gdb 把 row_id 修改為最大值:281474976710656
sudo gdb -p 16111 -ex 'p dict_sys->row_id=281474976710656' -batch
5、再插入三條數(shù)據(jù):
insert into t(age) values(4);
insert into t(age) values(5);
insert into t(age) values(6);
此事的數(shù)據(jù)庫數(shù)據(jù):
分析:
剛開始設置 row_id 為 1楷兽,插入三條數(shù)據(jù) 1地熄、2、3 的 row_id 也理應是 1芯杀、2端考、3;這是沒問題的揭厚。
接著設置 row_id 為最大值却特,緊跟著插入三條數(shù)據(jù)。這時的數(shù)據(jù)庫結果是:4筛圆、5裂明、6、3太援;你會發(fā)現(xiàn) 1闽晦、2 被覆蓋了。
row_id 達到后最大值后插入的值 4提岔、5仙蛉、6 的 row_id 分別是 0、1碱蒙、2荠瘪;由于 row_id 為 1、2 的值已存在,所以后者的值 5哀墓、6 會覆蓋掉 row_id 為 1鞭莽、2 的值。
結論:row_id 達到最大值后會從 0 重新開始算麸祷;前面插入的數(shù)據(jù)就會被后插入的數(shù)據(jù)覆蓋,且不會報錯褒搔。
04 總結
數(shù)據(jù)庫自增主鍵用完后分兩種情況:
- 有主鍵阶牍,報主鍵沖突
- 無主鍵,InnDB 會自動生成一個全局的row_id星瘾。它到達最大值后會從 0 開始算走孽,遇到 row_id 一樣時,新數(shù)據(jù)覆蓋舊數(shù)據(jù)琳状。所以磕瓷,我們還是盡量給表設置主鍵。
我的回答除了以上解決方法外念逞,還提到在業(yè)務開發(fā)中困食,我們不會等到主鍵用完那天就已經分庫分表了,基本不會遇到這種情況翎承。