MySQL一個表的自增id用完了,背井大佬讓我用這些姿勢再往裏插數據

在之前有篇文章中,和大家探討了在MySOL數據庫中,一個表的自增id用完,再插入數據有什麼問題?評論處有大佬建議我另開一篇再說一下表的自增id在用完的情況下,用replace into、insert ignore以及insert... on duplicate會發生什麼,當時不假思索就回復了安排,結果這一拖就快一個月了。

 

海嶽尚可傾,吐諾終不移。今天想到這件事,就立馬先把標題寫出來了,這樣就能督促自己把這篇文章完成,你以爲我是不想失信於大佬嗎?是的,我不想!但同時,我更不想失信於自己!

 

言歸正傳,開始我們今天的主題,當Mysql的一張表自增id用完了,此時再以各種姿勢向表裏插入數據會發生些什麼呢?上一篇文章覺得說的不太好,有一些我想介紹的知識點沒有講到,所以通過這篇跟大家一起通過實踐詳細的探討一番。

 

先創建一個簡單的表,只包含一個自增字段id,像這樣,勾選上自動遞增和無符號

此時我們可以看到右邊的建表語句,注意一點,類型int(10)後面多了一個unsigned關鍵字,不知道你們是否都知道unsigned是什麼意思,又是怎麼出來的,如果不知道,就要好好看下去了,這是一個很重要的知識點。

 

 

unsigned

 

 

首先,勾選上左側的無符號纔會出現unsigned類型。那unsigned是什麼意思呢?整型的每一種都有無符號(unsigned)和有符號(signed)兩種類型,在默認情況下聲明的整型變量都是有符號的類型(char有點特別)。由於在計算機中,整數是以補碼形式存放的。根據最高位的不同,如果是1,有符號數的話就是負數;如果是無符號數,則都解釋爲正數。同時在相同位數的情況下,所能表達的整數範圍變大。另外,unsigned若省略後一個關鍵字,大多數編譯器都會認爲是unsigned int

 

有沒有不知所云或者不明覺厲,簡單點解釋就是,在mysql中創建表時,如果整型使用了unsigned類型,那麼該數據項就永遠是正整數,每種數值類型的名稱和取值範圍如下表所示:

 

 

下面通過sql實例演示一下,我們先去掉勾選無符號,建表語句變成

這時候已經沒有unsigned了,我們向表裏插入兩條數據,一條正數一條負數:

INSERT INTO t VALUES (1);INSERT INTO t VALUES (-1);

結果顯示插入成功

表裏也有數據:

 

 

接下來我們修改表結構,勾選上無符號使用unsigned類型,像最初那樣

 

這時候我們再向表裏插入兩條數據,一條正數一條負數:​​​​​​​

INSERT INTO t VALUES (1);INSERT INTO t VALUES (-1);

看看執行結果:

 

發現-1不能插入表中,報錯超出了範圍值,因爲使用了unsigned之後,int類型的值範圍變成了0到4294967295(0 到232 - 1)4個字節,參考上面的表格:

 

 

 

介紹了unsigned無符號類型之後,那大家應該都知道了使用了int unsigned類型字段數據理論上最大爲4294967295,那麼接下來我們就挑戰極限,看看當一張表自增id用完了,此時再以各種姿勢向表裏插入數據會發生些什麼?

 

 

 

姿勢一:insert into

 

 

我們先直接暴力輸出,在創建表的時候,直接將AUTO_INCREMENT的初始值聲明爲4294967295,sql如下:​​​​​​​

CREATE TABLE `t` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4294967295 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

 

然後我們友好的向表中插一條數據:

INSERT INTO t VALUES (NULL);

 

你們猜一猜會發生什麼,能插入成功嗎?成功後表裏長什麼樣子?先看一下執行結果:

 

咦,成功了,什麼情況,我們打開表看看:

 

這是什麼鬼?我不是插入的null嗎?

 

先解釋第一個疑問,廢話,當然能插進去了,因爲AUTO_INCREMENT=4294967295是從這個開始,當然還能插進去,爲什麼插入null變成了4294967295,因爲只有一個字段,插入的null又不是id的值,id是自增主鍵啊,現在明白了吧,有可能有人覺得我這兩個問題好無聊,但是我真的見過有不少人不知道,不信你以後面試時可以隨機問問。

 

高潮來了,我們這時候再插入一條數據呢,還能插進去嗎?走起:

INSERT INTO t VALUES (NULL);

 

看結果,oh no,報主鍵衝突了:

 

這是爲什麼呢?上面我故意只截圖了一半,我們看看第一次插入null成功,我們打開表後看看錶的DDL語句:

可以看到,當再次插入時,使用的自增ID還是 4294967295,這時候就會報主鍵衝突的錯誤。

 

其實4294967295這個數字已經可以滿足大部分的應用場景了,如果你的服務會經常性的插入和刪除數據的話,還是存在用完的風險,建議採用bigint unsigned,這個數字就大了。

 

 

姿勢二:replace insert

 

如果表的自增id用完時,我們使用replace into繼續插數據時,會發生什麼呢?我們先直接執行:

REPLACE INTO t VALUES (NULL);

可以發現和之前報一樣的錯,都是主鍵衝突。

 

那麼,是不是insert into和replace into這兩條sql在自增id用完這種情況下,繼續插入數據時發生了一樣的遭遇呢?

 

我們下來看一下自增id未滿的情況下,這兩條sql是怎樣執行的。

 

1、新建一張表,自增id就從1開始(默認也是從1)​​​​​​​

DROP TABLE IF EXISTS t1;CREATE TABLE `t1` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,`num` int(10) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

 

2、先使用insert into插入一條數據:

INSERT INTO t1 VALUES (1,1);

查看結果:

看一下表結果:

 

3、再使用replace into:

REPLACE INTO t1 VALUES (1,1);

查看結果:

看一下表結果:

 

4、再使用replace into:

REPLACE INTO t1 VALUES (1,2);

查看執行結果:

再看一下表結果:

 

看到這兒你有發現什麼了嗎?第一次insert into和replace into執行後受影響的行都是1行,第二次執行replace into後受影響的行變成了2行,爲什麼呢?這就是replace into和insert into的不同之處了。

 

劃重點來了:

replace into 跟 insert 功能類似,不同點在於:replace into 首先嚐試插入數據到表中,這時候:

  1. 如果發現表中已經有此行數據(根據主鍵或者唯一索引判斷)則先刪除此行數據,然後插入新的數據。

  2. 否則,直接插入新數據。

 

所以,最後一次執行replace into時受影響的行變成了2行。知道了replace into的執行原理後,我們回到自增id最大時,replace into報錯,發生了什麼呢?如果發現表中已經有了最大的id,會先刪除這條數據,然後重新插入,但是此時雖然刪除了此條數據,自增id依然是最大值,爲什麼呢?

 

這就是自增主鍵沒有持久化的bug。究其原因,在於自增主鍵的分配,是由InnoDB數據字典內部一個計數器來決定的,而該計數器只在內存中維護,並不會持久化到磁盤中,所以還會報主鍵衝突的錯誤。

 

 

姿勢三:insert ignore into

 

insert into ignore就比較簡單了,我們直接運行sql看結果:

insert ignore into t VALUES (NULL);

結果顯示:

 

可以看到受影響的行爲0,這就是insert ignore 的作用:

  1. 如果發現表中已經有此行數據(根據主鍵或者唯一索引判斷)則跳過此查詢,不對數據庫作任何操作;

  2. 否則沒有此行數據的話,直接插入新數據。

 

 

姿勢四:insert ...on duplicate

 

 

老樣子,我們先通過sql實例看看自增id最大時,使用insert... on duplicate key會發生什麼,sql運行起來:

INSERT INTO t VALUES (NULL) ON DUPLICATE KEY UPDATE id =id+1;

查看運行結果:

依舊是主鍵衝突錯誤。

 

下面我們來簡單分析一下INSERT ON DUPLICATE KEY UPDATE這條sql做了什麼事。

 

我們在日常業務開發中經常有這樣一個場景,首先創建一條記錄,然後插入到數據庫;如果數據庫已經存在同一主鍵的記錄,則執行update操作,如果不存在,則執行insert操作,這個時候INSERT ON DUPLICATE KEY UPDATE就派上用場了。

 

在MySQL數據庫中,如果在insert語句後面帶上ON DUPLICATE KEY UPDATE 子句,而要插入的行與表中現有記錄的惟一索引或主鍵中產生重複值,那麼就會發生舊行的更新;如果插入的行數據與現有表中記錄的唯一索引或者主鍵不重複,則執行新紀錄插入操作。另外,ON DUPLICATE KEY UPDATE不能寫where條件。

 

以上就是當Mysql的一張表自增id用完了,此時再以各種姿勢向表裏插入數據會發生什麼的一些實踐探討,同時也加了其他一些知識點的講解,感謝尤慕大佬的建議,讓我在漫漫路上多了一些求索的動力。

 

 

知識拓展

 

如果在創建表時沒有顯示聲明主鍵,會怎麼辦呢?

如果是這種情況,InnoDB會自動幫你創建一個不可見的、長度爲6字節的row_id,而且InnoDB 維護了一個全局的 dictsys.row_id,所以未定義主鍵的表都共享該row_id,每次插入一條數據,都把全局row_id當成主鍵id,然後全局row_id加1,該全局row_id在代碼實現上使用的是bigint unsigned類型,但實際上只給row_id留了6字節,這種設計就會存在一個問題:如果全局row_id一直漲,一直漲,直到2的48冪次-1時,這個時候再+1,row_id的低48位都爲0,結果在插入新一行數據時,拿到的row_id就爲0,存在主鍵衝突的可能性。

所以,爲了避免這種隱患,每個表都需要定一個主鍵。

 

最後,留下一個問題,大家知道int(0)和int(10)有什麼區別嗎?

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