面試官居然問我自增主鍵是否連續(xù)


近期聽聞寒潮來臨帖蔓,作為一個熱愛學(xué)習(xí)的人企软,勇于面對疾風(fēng)庐扫,在第二次寒潮來臨時,與某大廠面試官鏖戰(zhàn)了近1個多小時后仗哨,本以為大獲全勝形庭,誰料大佬隨口給來一記重拳:自增主鍵連續(xù)嗎?

我們都知道厌漂,創(chuàng)建表時一般都會寫上id int(11) NOT NULL AUTO_INCREMENT代表主鍵的連續(xù)性萨醒,那么當(dāng)然要毫不客氣的告訴他,連續(xù)苇倡!必須連續(xù)富纸!
如果這么說了,那么恭喜你雏节,可能就掛了啊胜嗓,兄dei.
聽我娓娓道來
首先你要這么告訴他

1.主鍵連續(xù)性的意義

自增主鍵可以讓主鍵索引盡量地保持遞增順序插入,避免了頁分裂钩乍,索引更緊湊辞州。所以一般都會設(shè)置為主鍵自增。

但并不是所有情況下自增主鍵都會連續(xù)寥粹。下面我會舉幾個例子來說明下变过,什么情況下,主鍵自增但不是連續(xù)的涝涤。
在這之前呢媚狰,我大大方方的告訴面試官了2個事情,自增值保存在哪里阔拳,自增值是如何修改的崭孤。

2.自增值保存在哪里

先來構(gòu)建一張表

CREATE TABLE `user2` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',
  `name` varchar(32) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '姓名',
  `age` int(6) NOT NULL DEFAULT '0',
  `phone` varchar(24) NOT NULL DEFAULT '',
  `ctime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `mtime` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=0 DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='用戶信息'

AUTO_INCREMENT=0 證明下一個插入的值ID應(yīng)該從1開始
其實,這個輸出結(jié)果容易引起這樣的誤解:自增值是保存在表結(jié)構(gòu)定義里的。實際上辨宠,表的結(jié)構(gòu)定義存放在后綴名為.frm 的文件中遗锣,但是并不會保存自增值。

那么到底放在哪里呢嗤形?
不同的引擎對于自增值的保存策略不同精偿。

  • MyISAM 引擎的自增值保存在數(shù)據(jù)文件中。
  • InnoDB 引擎的自增值赋兵,保存在了內(nèi)存里笔咽,并且到了 MySQL 8.0 版本后,才有了“自增值持久化”的能力霹期,也就是才實現(xiàn)了“如果發(fā)生重啟叶组,表的自增值可以恢復(fù)為 MySQL 重啟前的值”,具體情況是:
    • 在 MySQL 5.7 及之前的版本经伙,自增值保存在內(nèi)存里扶叉,并沒有持久化。每次重啟后帕膜,第一次打開表時枣氧,都會去找自增值的最大值 max(id),然后將 max(id)+1 作為這個表當(dāng)前的自增值垮刹。舉例來說达吞,如果一個表當(dāng)前數(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 恢復(fù)重啟之前的值麦箍。

3.自增值怎么修改(what, how, when )

3.1如何修改

當(dāng)定義主鍵為AUTO_INCREMENT時陶珠,插入一條數(shù)據(jù)時挟裂,會做如下行為
1.當(dāng)插入數(shù)據(jù)時,ID指定為0 或者 null時揍诽,就把這個表當(dāng)前的AUTO_INCREMENT填寫到自增字段ID上
2.如果ID指定了值诀蓉,就用指定的值插入栗竖。

3.2 什么時候修改

1.首先插入insert into user2(id,name, age, phone) values (null, '主鍵', 12, '32432432');,因為沒有指定id,先獲取表的自增值當(dāng)做主鍵(假設(shè)此時為1)
2.將傳入的值改為insert into user2(id,name, age, phone) values (1,'主鍵', 12, '32432432');
3.將自增值改為2
4.繼續(xù)執(zhí)行操作

可以看到渠啤,這個表的自增值改成 2划滋,是在真正執(zhí)行插入數(shù)據(jù)的操作之前。

4.自增主鍵不連續(xù)的情況

4.1 事務(wù)回滾

假設(shè)主鍵連續(xù)的情況下埃篓,t5時刻和t3 時刻的主鍵都應(yīng)該是1,因為事務(wù)A 發(fā)生了回滾根资,假如沒有發(fā)生回滾架专,那么事物B的ID應(yīng)該是4.
看效果:


結(jié)果卻是主鍵不是1而是4,為什么會是這樣呢玄帕?
為什么在出現(xiàn)回滾的時候部脚,MySQL 沒有把表 的自增值改回去呢?如果把表 的當(dāng)前自增值從 4 改回 1裤纹,再插入新數(shù)據(jù)的時候委刘,不就可以生成 id=2 的一行數(shù)據(jù)了嗎?其實鹰椒,MySQL 這么設(shè)計是為了提升性能锡移。接下來,分析一下這個設(shè)計思路漆际,看看自增值為什么不能回退淆珊。

假設(shè)有兩個并行執(zhí)行的事務(wù),在申請自增值的時候奸汇,為了避免兩個事務(wù)申請到相同的自增 id施符,肯定要加鎖,然后順序申請擂找。

  1. 假設(shè)事務(wù) A 申請到了 id=1戳吝,2,3贯涎, 事務(wù) B 申請到 id=4听哭,那么這時候表的自增值是 5,之后繼續(xù)執(zhí)行柬采。
  2. 事務(wù) B 正確提交了欢唾,但事務(wù) A 出現(xiàn)了回滾
  3. 如果允許事務(wù) A 把自增 id 回退,也就是把表 的當(dāng)前自增值改回 2粉捻,那么就會出現(xiàn)這樣的情況:表里面已經(jīng)有 id=4的行礁遣,而當(dāng)前的自增 id 值是 2。
  4. 接下來肩刃,繼續(xù)執(zhí)行的其他事務(wù)就會申請到 id=2祟霍,然后再申請到 id=3杏头,最后到ID=4。這時沸呐,就會出現(xiàn)插入語句報錯“主鍵沖突”醇王。

那么如果要解決這個問題,有2種可行方案:

  1. 每次申請 id 之前崭添,先判斷表里面是否已經(jīng)存在這個 id寓娩。如果存在,就跳過這個 id呼渣。但是棘伴,這個方法的成本很高。因為屁置,本來申請 id 是一個很快的操作焊夸,現(xiàn)在還要再去主鍵索引樹上判斷 id 是否存在。

  2. 把自增 id 的鎖范圍擴(kuò)大蓝角,必須等到一個事務(wù)執(zhí)行完成并提交阱穗,下一個事務(wù)才能再申請自增 id。這個方法的問題使鹅,就是鎖的粒度太大揪阶,系統(tǒng)并發(fā)能力大大下降。

可見患朱,這兩個方法都會導(dǎo)致性能問題遣钳。造成這些麻煩的罪魁禍?zhǔn)祝褪俏覀兗僭O(shè)的這個“允許自增 id 回退”的前提導(dǎo)致的麦乞。因此蕴茴,InnoDB 放棄了這個設(shè)計,語句執(zhí)行失敗也不回退自增 id姐直。也正是因為這樣倦淀,所以才只保證了自增 id 是遞增的,但不保證是連續(xù)的声畏。

所以你會看到當(dāng)事務(wù)A執(zhí)行完撞叽,但未提交時,事務(wù)B 的show create table user2AUTO_INCREMENT=4也就證實了這個說法插龄。

4.2 唯一健沖突

其實當(dāng)唯一健沖突時愿棋,也同樣發(fā)生了回滾,所以具體情況和4.1所說的事務(wù)回滾一樣均牢。

4.3自增主鍵的批量申請

上SQL

insert into user2 (id,name,age)values(null, 1,1);
insert into user2 (id,name,age) values(null, 2,2);
insert into user2 (id,name,age) values(null, 3,3);
insert into user2 (id,name,age) values(null, 4,4);
create table user3 like user2;
insert into user3(name,age) select name,age from user2;
show create  table user3;
insert into user3 (id,name,age) values(null, 5,5);
show create  table user3;

結(jié)果:


可以看到此時主鍵發(fā)生了不連續(xù)性糠雨。
這是什么情況呢。那么一起來看下主鍵鎖吧徘跪。

5.自增鎖

5.1自增鎖的設(shè)計歷史

在 MySQL 5.0 版本的時候甘邀,自增鎖的范圍是語句級別琅攘。也就是說,如果一個語句申請了一個表自增鎖松邪,這個鎖會等語句執(zhí)行結(jié)束以后才釋放坞琴。顯然,這樣設(shè)計會影響并發(fā)度逗抑。

MySQL 5.1.22 版本引入了一個新策略剧辐,新增參數(shù) innodb_autoinc_lock_mode,默認(rèn)值是 1邮府。

  • 這個參數(shù)的值被設(shè)置為 0 時浙于,表示采用之前 MySQL 5.0 版本的策略,即語句執(zhí)行結(jié)束后才釋放鎖挟纱;
  • 這個參數(shù)的值被設(shè)置為 1 時:普通 insert 語句,自增鎖在申請之后就馬上釋放腐宋;類似 insert … select 這樣的批量插入數(shù)據(jù)的語句紊服,自增鎖還是要等語句結(jié)束后才被釋放;
  • 這個參數(shù)的值被設(shè)置為 2 時胸竞,所有的申請自增主鍵的動作都是申請后就釋放鎖欺嗤。

當(dāng)我說完這個的時候,隱約像是給自己挖了一個大坑卫枝,面試官抓住機(jī)會問到:為什么默認(rèn)設(shè)置下煎饼,insert … select 要使用語句級的鎖?為什么這個參數(shù)的默認(rèn)值不是 2校赤?

大大方方的告訴他吆玖,這么設(shè)計是為了數(shù)據(jù)一致性
回想下4.3的SQL


在這個例子里马篮,我往表 user2中插入了 4 行數(shù)據(jù)沾乘,然后創(chuàng)建了一個相同結(jié)構(gòu)的表 user3,然后兩個 session 同時執(zhí)行向表 user3 中插入數(shù)據(jù)的操作浑测。

你可以設(shè)想一下翅阵,如果 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)。

你可能會說擎值,這也沒關(guān)系吧慌烧,畢竟 session B 的語義本身就沒有要求表 t2 的所有行的數(shù)據(jù)都跟 session A 相同。是的鸠儿,從數(shù)據(jù)邏輯上看是對的屹蚊。但是,如果我們現(xiàn)在的 binlog_format=statement进每,你可以設(shè)想下汹粤,binlog 會怎么記錄呢?

binlog 拿去從庫執(zhí)行田晚,或者用來恢復(fù)臨時實例嘱兼,備庫和臨時實例里面,session B 這個語句執(zhí)行出來贤徒,生成的結(jié)果里面芹壕,id 都是連續(xù)的。這時接奈,這個庫就發(fā)生了數(shù)據(jù)不一致踢涌。

其實,這是因為原庫 session B 的 insert 語句序宦,生成的 id 不連續(xù)睁壁。這個不連續(xù)的 id,用 statement 格式的 binlog 來串行執(zhí)行互捌,是執(zhí)行不出來的潘明。

所以解決辦法如下2種:
1.讓原庫的批量插入數(shù)據(jù)語句,固定生成連續(xù)的 id 值秕噪。所以钉疫,自增鎖直到語句執(zhí)行結(jié)束才釋放,就是為了達(dá)到這個目的巢价。

2.在 binlog 里面把插入數(shù)據(jù)的操作都如實記錄進(jìn)來牲阁,到備庫執(zhí)行的時候,不再依賴于自增主鍵去生成壤躲。這種情況城菊,其實就是 innodb_autoinc_lock_mode 設(shè)置為 2倍踪,同時 binlog_format 設(shè)置為 row盗忱。

因此,在生產(chǎn)上妖泄,尤其是有 insert … select 這種批量插入數(shù)據(jù)的場景時漏麦,從并發(fā)插入數(shù)據(jù)性能的角度考慮客税,建議設(shè)置:innodb_autoinc_lock_mode=2 况褪,并且 binlog_format=row. 這樣做,既能提升并發(fā)性更耻,又不會出現(xiàn)數(shù)據(jù)一致性問題测垛。(到這里不信面試官不給你豎個大拇指。)

但是秧均,在普通的 insert 語句里面包含多個 value 值的情況下食侮,即使 innodb_autoinc_lock_mode 設(shè)置為 1,也不會等語句執(zhí)行完成才釋放鎖目胡。因為這類語句在申請自增 id 的時候锯七,是可以精確計算出需要多少個 id 的,然后一次性申請誉己,申請完成后鎖就可以釋放了眉尸。也就是說,批量插入數(shù)據(jù)的語句巨双,之所以需要這么設(shè)置噪猾,是因為“不知道要預(yù)先申請多少個 id”。

既然預(yù)先不知道要申請多少個自增 id炉峰,那么一種直接的想法就是需要一個時申請一個。但如果一個 select … insert 語句要插入 10 萬行數(shù)據(jù)脉执,按照這個邏輯的話就要申請 10 萬次疼阔。顯然,這種申請自增 id 的策略半夷,在大批量插入數(shù)據(jù)的情況下婆廊,不但速度慢,還會影響并發(fā)插入的性能巫橄。

因此淘邻,對于批量插入數(shù)據(jù)的語句,MySQL 有一個批量申請自增 id 的策略:

1.語句執(zhí)行過程中湘换,第一次申請自增 id宾舅,會分配 1 個;
2.1 個用完以后彩倚,這個語句第二次申請自增 id筹我,會分配 2 個;
2.2 個用完以后帆离,還是這個語句蔬蕊,第三次申請自增 id,會分配 4 個哥谷;
3.依此類推岸夯,同一個語句去申請自增 id麻献,每次申請到的自增 id 個數(shù)都是上一次的兩倍。
所以你看4.3的結(jié)果圖自增主鍵已經(jīng)到了8

現(xiàn)在這般猜扮,面試官漏出了滿意的笑容勉吻。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市破镰,隨后出現(xiàn)的幾起案子餐曼,更是在濱河造成了極大的恐慌,老刑警劉巖鲜漩,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件源譬,死亡現(xiàn)場離奇詭異,居然都是意外死亡孕似,警方通過查閱死者的電腦和手機(jī)踩娘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喉祭,“玉大人养渴,你說我怎么就攤上這事》豪樱” “怎么了理卑?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蔽氨。 經(jīng)常有香客問我藐唠,道長,這世上最難降的妖魔是什么鹉究? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任宇立,我火速辦了婚禮,結(jié)果婚禮上自赔,老公的妹妹穿的比我還像新娘妈嘹。我一直安慰自己,他們只是感情好绍妨,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布润脸。 她就那樣靜靜地躺著,像睡著了一般他去。 火紅的嫁衣襯著肌膚如雪津函。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天孤页,我揣著相機(jī)與錄音尔苦,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛允坚,可吹牛的內(nèi)容都是我干的魂那。 我是一名探鬼主播,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼稠项,長吁一口氣:“原來是場噩夢啊……” “哼涯雅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起展运,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤活逆,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拗胜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蔗候,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年埂软,在試婚紗的時候發(fā)現(xiàn)自己被綠了锈遥。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡勘畔,死狀恐怖所灸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情炫七,我是刑警寧澤爬立,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站万哪,受9級特大地震影響侠驯,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜壤圃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一陵霉、第九天 我趴在偏房一處隱蔽的房頂上張望琅轧。 院中可真熱鬧伍绳,春花似錦、人聲如沸乍桂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睹酌。三九已至权谁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間憋沿,已是汗流浹背旺芽。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人采章。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓运嗜,卻偏偏與公主長得像,于是被迫代替她去往敵國和親悯舟。 傳聞我的和親對象是個殘疾皇子担租,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

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