39 自增主鍵爲何不連續
Mysql的innodb的自增主鍵,因爲自增主鍵可讓主鍵索引儘可能得保持遞增順序插入,避免了頁分裂,所以索引更緊湊。mysql
在設計的時候,自增主鍵是不能保證連續的。算法
| t39 | CREATE TABLE `t39` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c` (`c`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8
自增值保存在什麼地方
上表插入一行值sql
(system@127.0.0.1:3306) [test]> insert into t39 values(null,1,1); 在執行show create tables | t39 | CREATE TABLE `t39` ( `id` int(11) NOT NULL AUTO_INCREMENT, `c` int(11) DEFAULT NULL, `d` int(11) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `c` (`c`) ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 |
看到AUTO_INCREMENT=2,表示下一次插入數據時,若是須要自動生成自增值,會生成id=2。併發
實際上,表的結構定義存放在後綴名爲.frm的文件中,可是並不會保存自增值。性能
不一樣的引擎對於自增值的報錯策略不一樣優化
--myisam引擎的自增值保存在數據文件中spa
--innodb引擎的自增值,實際上是保存在內存裏,而且到了mysql8.0版本後,纔有了」自增值持久化」的能力,若是發生重啓,表的自增值能夠恢復到mysql重啓前的值設計
---mysql5.7及以前的版本,自增值保存在內存中,並沒有持久化,每次重啓,第一次打開表的時候,都會去找自增值的最大值max(id),而後將max(id)+1做爲這個表當前自增的值。code
---在mysql8.0版本後,將自增值的變動記錄在了redo log中,重啓的時候依靠redo log恢復重啓以前的值。orm
自增值修改機制
在mysql裏面,若是字段id被定義爲auto_increment,在插一行數據的時候,自增值的行爲以下:
--1 若是插入數據時id字段定義爲0、null或未指定值,那麼就把這個表當前auto_increment的值填到自增字段。
--2若是插入數據是id字段指定了具體的值,就直接使用語句指定的值。
根據要插入的值和當前自增值的大小關係,自增值的變動結果也會有所不一樣,假設,某次要插入的值是x,當前自增的值是y
---1 x<y,那麼這個表的自增值不變
---2 x>=y,就須要把當前自增值修改成新的自增值。
新的自增值生成算法是:從auto_increment_offset開始,以auto_increment_increment爲步長,持續疊加,直到找到第一個大於x的值,做爲新的自增值
自增值的修改時機
假設表t39已經有一條記錄(1,1,1),咱們在執行一個insert
(system@127.0.0.1:3306) [test]> insert into t39 values(null,1,1); ERROR 1062 (23000): Duplicate entry '1' for key 'c'
這個語句的執行流程:
--1執行器調用innodb引擎接口寫入一行,傳入的這一行的值是(0,1,1)
--2 innodb發現用戶沒有指定自增id的值,獲取表t39當前的自增值是2
--3 將傳入的行的值改爲(2,1,1,)
--4 將表的自增值改爲3
--5繼續執行插入數據操做,因爲已經存在c=1,因此報錯duplicate值
能夠看到,這個表的自增值改爲了3,是在真正執行插入操做的時候,可是這個語句因爲報錯,id=2的這一行並無插入成功,但也沒有將自增值改回去。
當咱們在插入一行的時候,自增值變成了3
能夠看到,這個操做序列復現了一個自增主鍵id不連續的現場,可見,惟一鍵衝突是致使自增主鍵id不連續的一個緣由。
一樣的,事務回滾也會產生相似的現象。
insert into t39 values(null,1,1); begin; insert into t39 values(null,2,2); rollback; insert into t39 values(null,2,2); // 插入的行是 (3,2,2)
其實,mysql不把自增是回退,是爲了提高性能。
假設有兩個並行執行的事務,在申請自增值的時候,爲了不兩個事務申請到相同的自增id,確定是要加鎖,而後順序申請
--1 加鎖事務a申請到了id=2,事務b申請到了id=3,這時候表t的自增是4,以後繼續執行
--2 事務b正確提交了,可是事務a出現了惟一性衝突
--3 若是容許事務a把自增id退回,也就是把表t當前的自增值改回2,那麼表裏的狀況,表裏已經有了id=3的行,而當前的自增id是2
--4 接下來,繼續執行的其餘事務就會申請到id=2,而後在申請到id=3時,就會報錯主鍵衝突。
而爲了解決這個主鍵衝突,有兩種辦法
--1 每次申請id以前,先判斷表裏面是否已經存在這個id,若是存在,就跳過這個id,但這個方法成本很高,要去主鍵索引樹上判斷id是否存在
--2 把自增id的鎖範圍擴大,必須等到一個事務執行完並提交,下一個事務才能申請id,這個鎖的粒度太大,系統併發能力大大降低。
因此innodb的自增id保證了遞增,但不保證是連續
自增鎖的優化
Mysql參數innodb_autoinc_lock_mode,默認是1
--1 這個參數爲0是,表示採用以前的mysql 5.0版本的設計,即語句執行結束後才釋放鎖
--2 這個參數位1
---普通的insert語句,自增鎖在申請後就立馬釋放
---相似insert。。。Select這樣批量插入,自增鎖仍是要等語句結束後才被釋放
--3 參數爲2時,全部的申請自增主鍵的動做都是申請後就釋放。
SESSION A |
SESSION B |
> insert into t39 values(null,1,1); > insert into t39 values(null,2,2); > insert into t39 values(null,3,3); > insert into t39 values(null,4,4); |
|
|
> create table t39_1 like t39; |
> insert into t39 values(null,5,5); |
> insert into t39_1(c,d) select c,d from t39; |
--1 在原庫的批量插入數據語句,固定生成連續的id值,因此,自增鎖直到語句執行結束後才釋放,
--2 在binlog裏面把數據的操做都記錄下來,到備庫去執行的時候,再也不依賴於自增主鍵去生成,其實就是innodb_autoinc_lock_mode=2,binlog_format=row
所以,在生產上,尤爲是有insert。。。Select這種批量插入數據的場景,但併發插入數據性能考慮,建議設置innodb_autoinc_lock_mode=2,binlog_format=row
須要注意的是,批量插入,包含的語句類型爲insert。。。Select,replace。。。Select和load data語句,是由於不知道預先申請多少個id
對於批量插入數據的語句,mysql有一個批量申請自增id的策略
--1 語句執行過程當中,第一次申請自增id,會分配1個
--2 1個用完之後,這個語句第二次申請自增id,會分配2個
--3 2個用完之後,仍是這個語句,第三次申請自增id,會分配4個
--4 依次類推,同一個語句去申請自增id,每次申請到的自增id個數都是上一次的兩倍
insert into t values(null, 1,1); insert into t values(null, 2,2); insert into t values(null, 3,3); insert into t values(null, 4,4); create table t2 like t; insert into t2(c,d) select c,d from t; insert into t2 values(null, 5,5);
Inset。。。Select,實際上往表t2中插入了4行數據,但,這4行數據是分三次申請的自增id,第一次申請到id=1,第二次被分配了id=2和id=3,第三次被分配了id=4到id=7,因爲這個語句實際上只有了4個id,因此id=5到7就浪費了。
最後執行insert into t2 values(null, 5,5);,實際上插入的數據是(8,5,5)
也是主鍵id出現自增id不連續的第三種緣由。