39 自增主鍵爲何不連續

39 自增主鍵爲何不連續

Mysqlinnodb的自增主鍵,因爲自增主鍵可讓主鍵索引儘可能得保持遞增順序插入,避免了頁分裂,所以索引更緊湊。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字段定義爲0null或未指定值,那麼就把這個表當前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的行,而當前的自增id2

--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。。。Selectreplace。。。Selectload 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=2id=3,第三次被分配了id=4id=7,因爲這個語句實際上只有了4id,因此id=57就浪費了。

最後執行insert into t2 values(null, 5,5);,實際上插入的數據是(8,5,5)

也是主鍵id出現自增id不連續的第三種緣由。

相關文章
相關標籤/搜索