MySQL實戰(zhàn)45講Day38----自增主鍵不是連續(xù)的原因

一喊熟、自增主鍵不連續(xù)的原因:

??對于表:

CREATE TABLE `t` (
  `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;

??其中id是自增主鍵字段、c是唯一索引姐刁。

1芥牌、自增值保存位置:

  1. 表的結(jié)構(gòu)定義存放在后綴名為.frm的文件中,不會保存自增值聂使。

  2. 不同的引擎對于自增值的保存策略不同壁拉。

    • MyISAM引擎的自增值保存在數(shù)據(jù)文件中。
    • InnoDB引擎的自增值柏靶,其實是保存在了內(nèi)存里弃理,并且到了MySQL 8.0版本后,才有了“自增值持久化”的能力屎蜓,也就是才實現(xiàn)了“如果發(fā)生重啟痘昌,表的自增值可以恢復為MySQL重啟前的值”,具體情況是:
      • 在MySQL 5.7及之前的版本炬转,自增值保存在內(nèi)存里辆苔,并沒有持久化。每次重啟后扼劈,第一次打開表的時候姑子,都會去找自增值的最大值max(id),然后將max(id)+1作為這個表當前的自增值测僵。舉例來說,如果一個表當前數(shù)據(jù)行里最大的id是10谢翎,AUTO_INCREMENT=11捍靠。這時候,我們刪除id=10的行森逮,AUTO_INCREMENT還是11榨婆。但如果馬上重啟實例,重啟后這個表的AUTO_INCREMENT就會變成10褒侧。也就是說良风,MySQL重啟可能會修改一個表的AUTO_INCREMENT的值谊迄。
      • 在MySQL 8.0版本,將自增值的變更記錄在了redo log中烟央,重啟的時候依靠redo log恢復重啟之前的值统诺。

2、自增值修改機制:

  1. 在MySQL里面疑俭,如果字段id被定義為AUTO_INCREMENT粮呢,在插入一行數(shù)據(jù)的時候,自增值的行為如下:

    • 如果插入數(shù)據(jù)時id字段指定為0钞艇、null 或未指定值啄寡,那么就把這個表當前的 AUTO_INCREMENT值填到自增字段;
    • 如果插入數(shù)據(jù)時id字段指定了具體的值哩照,就直接使用語句里指定的值挺物。
  2. 根據(jù)要插入的值和當前自增值的大小關(guān)系,自增值的變更結(jié)果也會有所不同飘弧。假設识藤,某次要插入的值是X,當前的自增值是Y眯牧。

    • 如果X<Y蹋岩,那么這個表的自增值不變;
    • 如果X≥Y学少,就需要把當前自增值修改為新的自增值剪个。
  3. 新的自增值生成算法是:從auto_increment_offset開始,以auto_increment_increment為步長版确,持續(xù)疊加扣囊,直到找到第一個大于X的值,作為新的自增值绒疗。其中侵歇,auto_increment_offset 和 auto_increment_increment是兩個系統(tǒng)參數(shù),分別用來表示自增的初始值和步長吓蘑,默認值都是1惕虑。
    注意:在一些場景下,使用的就不全是默認值磨镶。比如溃蔫,雙M的主備結(jié)構(gòu)里要求雙寫的時候,就可能會設置成auto_increment_increment=2琳猫,讓一個庫的自增id都是奇數(shù)伟叛,另一個庫的自增id都是偶數(shù),避免兩個庫生成的主鍵發(fā)生沖突脐嫂。

  4. 當auto_increment_offset和auto_increment_increment都是1的時候统刮,新的自增值的生成邏輯:

    • 如果準備插入的值>=當前自增值紊遵,新的自增值就是“準備插入的值+1”;
    • 否則侥蒙,自增值不變暗膜。

3、自增值的修改時機:

??假設表t里面已經(jīng)有了(1,1,1)這條記錄辉哥,這時再執(zhí)行一條插入數(shù)據(jù)命令:

insert into t values(null, 1, 1); 

<1>桦山、該語句的執(zhí)行流程:

  1. 執(zhí)行器調(diào)用InnoDB引擎接口寫入一行,傳入的這一行的值是(0,1,1);

  2. InnoDB發(fā)現(xiàn)用戶沒有指定自增id的值醋旦,獲取表t當前的自增值2恒水;

  3. 將傳入的行的值改成(2,1,1);

  4. 將表的自增值改成3;

  5. 繼續(xù)執(zhí)行插入數(shù)據(jù)操作饲齐,由于已經(jīng)存在c=1的記錄钉凌,所以報Duplicate key error,語句返回捂人。

    insert(null, 1,1)唯一鍵沖突

    ??可以看到御雕,這個表的自增值由2改成3,是在真正執(zhí)行插入數(shù)據(jù)的操作之前滥搭。這個語句真正執(zhí)行的時候酸纲,因為碰到唯一鍵c沖突,所以id=2這一行并沒有插入成功瑟匆,但也沒有將自增值再改回去闽坡。所以,在這之后愁溜,再插入新的數(shù)據(jù)行時疾嗅,拿到的自增id就是3。也就是說冕象,出現(xiàn)了自增主鍵不連續(xù)的情況代承。

<2>、完整復現(xiàn):

4渐扮、自增值不能回退的原因:

  1. 假設有兩個并行執(zhí)行的事務论悴,在申請自增值的時候,為了避免兩個事務申請到相同的自增id墓律,肯定要加鎖意荤,然后順序申請。
    • 假設事務A申請到了id=2只锻, 事務B申請到id=3,那么這時候表t的自增值是4紫谷,之后繼續(xù)執(zhí)行齐饮。
    • 事務B正確提交了捐寥,但事務A出現(xiàn)了唯一鍵沖突。
    • 如果允許事務A把自增id回退祖驱,也就是把表t的當前自增值改回2握恳,那么就會出現(xiàn)這樣的情況:表里面已經(jīng)有id=3的行,而當前的自增id值是2捺僻。
    • 接下來乡洼,繼續(xù)執(zhí)行的其他事務就會申請到id=2,然后再申請到id=3匕坯。這時束昵,就會出現(xiàn)插入語句報錯“主鍵沖突”。
  2. 對于主鍵沖突葛峻,有兩種方法解決方法:
    • 每次申請id之前锹雏,先判斷表里面是否已經(jīng)存在這個id。如果存在术奖,就跳過這個id礁遵。但是,這個方法的成本很高采记。因為佣耐,本來申請id是一個很快的操作,現(xiàn)在還要再去主鍵索引樹上判斷id是否存在唧龄。
    • 把自增id的鎖范圍擴大兼砖,必須等到一個事務執(zhí)行完成并提交,下一個事務才能再申請自增id选侨。這個方法的問題掖鱼,就是鎖的粒度太大,系統(tǒng)并發(fā)能力大大下降援制。
  3. 可見戏挡,這兩個方法都會導致性能問題。造成這些麻煩的罪魁禍首晨仑,就是我們假設的這個“允許自增id回退”的前提導致的褐墅。因此,InnoDB放棄了這個設計洪己,語句執(zhí)行失敗也不回退自增id妥凳。也正是因為這樣,所以才只保證了自增id是遞增的答捕,但不保證是連續(xù)的逝钥。

5、自增鎖的優(yōu)化:

<1>拱镐、自增鎖:

  1. 在MySQL 5.0版本的時候艘款,自增鎖的范圍是語句級別持际。也就是說,如果一個語句申請了一個表自增鎖哗咆,這個鎖會等語句執(zhí)行結(jié)束以后才釋放蜘欲。顯然,這樣設計會影響并發(fā)度晌柬。

  2. MySQL 5.1.22版本引入了一個新策略姥份,新增參數(shù)innodb_autoinc_lock_mode,默認值是1年碘。

    • 這個參數(shù)的值被設置為0時澈歉,表示采用之前MySQL 5.0版本的策略,即語句執(zhí)行結(jié)束后才釋放鎖盛泡;

    • 這個參數(shù)的值被設置為1時:

      • 普通insert語句闷祥,自增鎖在申請之后就馬上釋放;
      • 類似insert … select這樣的批量插入數(shù)據(jù)的語句傲诵,自增鎖還是要等語句結(jié)束后才被釋放凯砍;
    • 這個參數(shù)的值被設置為2時,所有的申請自增主鍵的動作都是申請后就釋放鎖拴竹。

<2>悟衩、默認設置下,insert … select 要使用語句級的鎖的原因:

??這么設計是為了數(shù)據(jù)的一致性栓拜。對于下面的場景:

批量插入數(shù)據(jù)的自增鎖
如果session B是申請了自增值以后馬上就釋放自增鎖座泳,那么就可能出現(xiàn)這樣的情況:

  • session B先插入了兩個記錄,(1,1,1)幕与、(2,2,2)挑势;
  • 然后,session A來申請自增id得到id=3啦鸣,插入了(3,5,5)潮饱;
  • 之后,session B繼續(xù)執(zhí)行诫给,插入兩條記錄(4,3,3)香拉、 (5,4,4)。

??此時就導致了session A和session B不一致中狂,而當binlog_format=statement時凫碌,由于兩個session是同時執(zhí)行插入數(shù)據(jù)命令的,所以binlog里面對表t2的更新日志只有兩種情況:要么先記session A的胃榕,要么先記session B的盛险。但不論是哪一種惧眠,這個binlog拿去從庫執(zhí)行性湿,或者用來恢復臨時實例川背,備庫和臨時實例里面届宠,session B這個語句執(zhí)行出來,生成的結(jié)果里面鸟蜡,id都是連續(xù)的。這時挺邀,這個庫就發(fā)生了數(shù)據(jù)不一致揉忘。

<3>、庫發(fā)生數(shù)據(jù)不一致的原因及解決方法:

(1)端铛、原因:

??因為原庫session B的insert語句泣矛,生成的id不連續(xù)。這個不連續(xù)的id禾蚕,用statement格式的binlog來串行執(zhí)行您朽,是執(zhí)行不出來的。

(2)换淆、解決方法:

  • 一種思路是哗总,讓原庫的批量插入數(shù)據(jù)語句,固定生成連續(xù)的id值倍试。所以讯屈,自增鎖直到語句執(zhí)行結(jié)束才釋放,就是為了達到這個目的县习。
  • 另一種思路是涮母,在binlog里面把插入數(shù)據(jù)的操作都如實記錄進來,到備庫執(zhí)行的時候躁愿,不再依賴于自增主鍵去生成叛本。這種情況,其實就是innodb_autoinc_lock_mode設置為2彤钟,同時binlog_format設置為row来候。

??因此,在生產(chǎn)上样勃,尤其是有批量插入數(shù)據(jù)的場景時吠勘,從并發(fā)插入數(shù)據(jù)性能的角度考慮,建議這樣設置:innodb_autoinc_lock_mode=2 峡眶,并且 binlog_format=row.這樣做剧防,既能提升并發(fā)性,又不會出現(xiàn)數(shù)據(jù)一致性問題辫樱。
需要注意的是峭拘,這里說的批量插入數(shù)據(jù),包含的語句類型是insert … select、replace … select和load data語句鸡挠。

<4>辉饱、對于批量插入數(shù)據(jù)的語句,MySQL有一個批量申請自增id的策略:

  • 語句執(zhí)行過程中拣展,第一次申請自增id彭沼,會分配1個;
  • 1個用完以后备埃,這個語句第二次申請自增id姓惑,會分配2個;
  • 2個用完以后按脚,還是這個語句于毙,第三次申請自增id,會分配4個辅搬;
  • 依此類推唯沮,同一個語句去申請自增id,每次申請到的自增id個數(shù)都是上一次的兩倍堪遂。

比如對于下面語句:

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);

??insert…select介蛉,實際上往表t2中插入了4行數(shù)據(jù)。但是蚤氏,這四行數(shù)據(jù)是分三次申請的自增id甘耿,第一次申請到了id=1,第二次被分配了id=2和id=3竿滨, 第三次被分配到id=4到id=7佳恬。由于這條語句實際只用上了4個id,所以id=5到id=7就被浪費掉了于游。之后毁葱,再執(zhí)行insert into t2 values(null, 5,5),實際上插入的數(shù)據(jù)就是(8,5,5)贰剥。

6倾剿、自增主鍵不連續(xù)的原因:

<1>、唯一鍵沖突是導致自增主鍵id不連續(xù)的第一種原因蚌成。

<2>前痘、事務回滾是導致自增主鍵id不連續(xù)的第二種原因。

<3>担忧、批量申請自增id的策略是導致自增主鍵id不連續(xù)的第三種原因芹缔。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓶盛,隨后出現(xiàn)的幾起案子最欠,更是在濱河造成了極大的恐慌示罗,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件芝硬,死亡現(xiàn)場離奇詭異蚜点,居然都是意外死亡,警方通過查閱死者的電腦和手機拌阴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門绍绘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人迟赃,你說我怎么就攤上這事脯倒。” “怎么了捺氢?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剪撬。 經(jīng)常有香客問我摄乒,道長,這世上最難降的妖魔是什么残黑? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任馍佑,我火速辦了婚禮,結(jié)果婚禮上梨水,老公的妹妹穿的比我還像新娘拭荤。我一直安慰自己,他們只是感情好疫诽,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布舅世。 她就那樣靜靜地躺著,像睡著了一般奇徒。 火紅的嫁衣襯著肌膚如雪雏亚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天摩钙,我揣著相機與錄音罢低,去河邊找鬼。 笑死胖笛,一個胖子當著我的面吹牛网持,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播长踊,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼功舀,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了之斯?” 一聲冷哼從身側(cè)響起日杈,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤遣铝,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后莉擒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體酿炸,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年涨冀,在試婚紗的時候發(fā)現(xiàn)自己被綠了填硕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鹿鳖,死狀恐怖扁眯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情翅帜,我是刑警寧澤姻檀,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站涝滴,受9級特大地震影響绣版,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜歼疮,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一杂抽、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧韩脏,春花似錦缩麸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至济竹,卻和暖如春痕檬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背送浊。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工梦谜, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人袭景。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓唁桩,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耸棒。 傳聞我的和親對象是個殘疾皇子荒澡,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容