騰訊面試:一條SQL語句執(zhí)行得很慢的原因有哪些妹笆?

一、開始裝逼:分類討論
一條 SQL 語句執(zhí)行的很慢娜氏,那是每次執(zhí)行都很慢呢拳缠?還是大多數(shù)情況下是正常的,偶爾出現(xiàn)很慢呢贸弥?所以我覺得窟坐,我們還得分以下兩種情況來討論。

1绵疲、大多數(shù)情況是正常的哲鸳,只是偶爾會出現(xiàn)很慢的情況。

2盔憨、在數(shù)據(jù)量不變的情況下徙菠,這條SQL語句一直以來都執(zhí)行的很慢。

針對這兩種情況郁岩,我們來分析下可能是哪些原因?qū)е碌摹?/p>

二婿奔、針對偶爾很慢的情況
一條 SQL 大多數(shù)情況正常芙盘,偶爾才能出現(xiàn)很慢的情況,針對這種情況脸秽,我覺得這條SQL語句的書寫本身是沒什么問題的,而是其他原因?qū)е碌暮牵菚鞘裁丛蚰兀?/p>

1记餐、數(shù)據(jù)庫在刷新臟頁(flush)我也無奈啊
當(dāng)我們要往數(shù)據(jù)庫插入一條數(shù)據(jù)、或者要更新一條數(shù)據(jù)的時(shí)候薇正,我們知道數(shù)據(jù)庫會在內(nèi)存中把對應(yīng)字段的數(shù)據(jù)更新了片酝,但是更新之后,這些更新的字段并不會馬上同步持久化到磁盤中去挖腰,而是把這些更新的記錄寫入到 redo log 日記中去雕沿,等到空閑的時(shí)候,在通過 redo log 里的日記把最新的數(shù)據(jù)同步到磁盤中去猴仑。

當(dāng)內(nèi)存數(shù)據(jù)頁跟磁盤數(shù)據(jù)頁內(nèi)容不一致的時(shí)候审轮,我們稱這個(gè)內(nèi)存頁為“臟頁”。內(nèi)存數(shù)據(jù)寫入到磁盤后辽俗,內(nèi)存和磁盤上的數(shù)據(jù)頁的內(nèi)容就一致了疾渣,稱為“干凈頁”。

刷臟頁有下面4種場景(后兩種不用太關(guān)注“性能”問題):

redolog寫滿了:redo log 里的容量是有限的崖飘,如果數(shù)據(jù)庫一直很忙榴捡,更新又很頻繁,這個(gè)時(shí)候 redo log 很快就會被寫滿了朱浴,這個(gè)時(shí)候就沒辦法等到空閑的時(shí)候再把數(shù)據(jù)同步到磁盤的吊圾,只能暫停其他操作,全身心來把數(shù)據(jù)同步到磁盤中去的翰蠢,而這個(gè)時(shí)候项乒,就會導(dǎo)致我們平時(shí)正常的SQL語句突然執(zhí)行的很慢,所以說梁沧,數(shù)據(jù)庫在在同步數(shù)據(jù)到磁盤的時(shí)候板丽,就有可能導(dǎo)致我們的SQL語句執(zhí)行的很慢了。

內(nèi)存不夠用了:如果一次查詢較多的數(shù)據(jù)趁尼,恰好碰到所查數(shù)據(jù)頁不在內(nèi)存中時(shí)埃碱,需要申請內(nèi)存,而此時(shí)恰好內(nèi)存不足的時(shí)候就需要淘汰一部分內(nèi)存數(shù)據(jù)頁酥泞,如果是干凈頁砚殿,就直接釋放,如果恰好是臟頁就需要刷臟頁芝囤。

MySQL 認(rèn)為系統(tǒng)“空閑”的時(shí)候:這時(shí)系統(tǒng)沒什么壓力似炎。

MySQL 正常關(guān)閉的時(shí)候:這時(shí)候辛萍,MySQL 會把內(nèi)存的臟頁都 flush 到磁盤上,這樣下次 MySQL 啟動的時(shí)候羡藐,就可以直接從磁盤上讀數(shù)據(jù)贩毕,啟動速度會很快。

2仆嗦、拿不到鎖我能怎么辦
這個(gè)就比較容易想到了辉阶,我們要執(zhí)行的這條語句,剛好這條語句涉及到的表瘩扼,別人在用谆甜,并且加鎖了,我們拿不到鎖集绰,只能慢慢等待別人釋放鎖了规辱。或者栽燕,表沒有加鎖罕袋,但要使用到的某個(gè)一行被加鎖了,這個(gè)時(shí)候碍岔,我也沒辦法啊炫贤。

如果要判斷是否真的在等待鎖,我們可以用 show processlist這個(gè)命令來查看當(dāng)前的狀態(tài)哦付秕,這里我要提醒一下兰珍,有些命令最好記錄一下,反正询吴,我被問了好幾個(gè)命令掠河,都不知道怎么寫,呵呵猛计。

下來我們來訪分析下第二種情況唠摹,我覺得第二種情況的分析才是最重要的

三、針對一直都這么慢的情況
如果在數(shù)據(jù)量一樣大的情況下奉瘤,這條 SQL 語句每次都執(zhí)行的這么慢勾拉,那就就要好好考慮下你的 SQL 書寫了,下面我們來分析下哪些原因會導(dǎo)致我們的 SQL 語句執(zhí)行的很不理想盗温。

我們先來假設(shè)我們有一個(gè)表藕赞,表里有下面兩個(gè)字段,分別是主鍵 id,和兩個(gè)普通字段 c 和 d卖局。

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `c` int(11) DEFAULT NULL,
  `d` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

1斧蜕、扎心了,沒用到索引
沒有用上索引砚偶,我覺得這個(gè)原因是很多人都能想到的批销,例如你要查詢這條語句

select * from t where 100 <c and c < 100000;

(1)洒闸、字段沒有索引

剛好你的 c 字段上沒有索引,那么抱歉均芽,只能走全表掃描了丘逸,你就體驗(yàn)不會索引帶來的樂趣了,所以掀宋,這回導(dǎo)致這條查詢語句很慢深纲。

(2)、字段有索引布朦,但卻沒有用索引

好吧,這個(gè)時(shí)候你給 c 這個(gè)字段加上了索引昼窗,然后又查詢了一條語句

select * from t where c - 1 = 1000;

我想問大家一個(gè)問題是趴,這樣子在查詢的時(shí)候會用索引查詢嗎?

答是不會澄惊,如果我們在字段的左邊做了運(yùn)算唆途,那么很抱歉,在查詢的時(shí)候掸驱,就不會用上索引了肛搬,所以呢,大家要注意這種字段上有索引毕贼,但由于自己的疏忽温赔,導(dǎo)致系統(tǒng)沒有使用索引的情況了。

正確的查詢應(yīng)該如下

select * from t where c = 1000 + 1;

有人可能會說鬼癣,右邊有運(yùn)算就能用上索引陶贼?難道數(shù)據(jù)庫就不會自動幫我們優(yōu)化一下,自動把 c - 1=1000 自動轉(zhuǎn)換為 c = 1000+1待秃。

不好意思拜秧,確實(shí)不會幫你,所以章郁,你要注意了枉氮。

(3)、函數(shù)操作導(dǎo)致沒有用上索引

如果我們在查詢的時(shí)候暖庄,對字段進(jìn)行了函數(shù)操作聊替,也是會導(dǎo)致沒有用上索引的,例如

select * from t where pow(c,2) = 1000;

這里我只是做一個(gè)例子培廓,假設(shè)函數(shù) pow 是求 c 的 n 次方佃牛,實(shí)際上可能并沒有 pow(c,2)這個(gè)函數(shù)。其實(shí)這個(gè)和上面在左邊做運(yùn)算也是很類似的医舆。

所以呢俘侠,一條語句執(zhí)行都很慢的時(shí)候象缀,可能是該語句沒有用上索引了,不過具體是啥原因?qū)е聸]有用上索引的呢爷速,你就要會分析了央星,我上面列舉的三個(gè)原因,應(yīng)該是出現(xiàn)的比較多的吧惫东。

2莉给、呵呵,數(shù)據(jù)庫自己選錯索引了
我們在進(jìn)行查詢操作的時(shí)候廉沮,例如

select * from t where 100 < c and c < 100000;

我們知道颓遏,主鍵索引和非主鍵索引是有區(qū)別的,主鍵索引存放的值是整行字段的數(shù)據(jù)滞时,而非主鍵索引上存放的值不是整行字段的數(shù)據(jù)叁幢,而且存放主鍵字段的值。詳見文末一張無敵腦圖坪稽。

也就是說曼玩,我們?nèi)绻?c 這個(gè)字段的索引的話,最后會查詢到對應(yīng)主鍵的值窒百,然后黍判,再根據(jù)主鍵的值走主鍵索引,查詢到整行數(shù)據(jù)返回篙梢。

好吧扯了這么多顷帖,其實(shí)我就是想告訴你,就算你在 c 字段上有索引渤滞,系統(tǒng)也并不一定會走 c 這個(gè)字段上的索引窟她,而是有可能會直接掃描掃描全表,找出所有符合 100 < c and c < 100000 的數(shù)據(jù)蔼水。
為什么會這樣呢震糖?

其實(shí)是這樣的,系統(tǒng)在執(zhí)行這條語句的時(shí)候趴腋,會進(jìn)行預(yù)測:究竟是走 c 索引掃描的行數(shù)少吊说,還是直接掃描全表掃描的行數(shù)少呢?顯然优炬,掃描行數(shù)越少當(dāng)然越好了颁井,因?yàn)閽呙栊袛?shù)越少,意味著I/O操作的次數(shù)越少蠢护。

如果是掃描全表的話雅宾,那么掃描的次數(shù)就是這個(gè)表的總行數(shù)了,假設(shè)為 n葵硕;而如果走索引 c 的話眉抬,我們通過索引 c 找到主鍵之后贯吓,還得再通過主鍵索引來找我們整行的數(shù)據(jù),也就是說蜀变,需要走兩次索引悄谐。而且,我們也不知道符合 100 c < and c < 10000 這個(gè)條件的數(shù)據(jù)有多少行库北,萬一這個(gè)表是全部數(shù)據(jù)都符合呢爬舰?這個(gè)時(shí)候意味著,走 c 索引不僅掃描的行數(shù)是 n寒瓦,同時(shí)還得每行數(shù)據(jù)走兩次索引情屹。

所以呢,系統(tǒng)是有可能走全表掃描而不走索引的杂腰。那系統(tǒng)是怎么判斷呢垃你?

判斷來源于系統(tǒng)的預(yù)測,也就是說颈墅,如果要走 c 字段索引的話蜡镶,系統(tǒng)會預(yù)測走 c 字段索引大概需要掃描多少行雾袱。如果預(yù)測到要掃描的行數(shù)很多恤筛,它可能就不走索引而直接掃描全表了。

那么問題來了芹橡,系統(tǒng)是怎么預(yù)測判斷的呢毒坛?這里我給你講下系統(tǒng)是怎么判斷的吧,雖然這個(gè)時(shí)候我已經(jīng)寫到脖子有點(diǎn)酸了林说。

系統(tǒng)是通過索引的區(qū)分度來判斷的煎殷,一個(gè)索引上不同的值越多,意味著出現(xiàn)相同數(shù)值的索引越少腿箩,意味著索引的區(qū)分度越高豪直。我們也把區(qū)分度稱之為基數(shù),即區(qū)分度越高珠移,基數(shù)越大弓乙。所以呢,基數(shù)越大钧惧,意味著符合 100 < c and c < 10000 這個(gè)條件的行數(shù)越少暇韧。

所以呢,一個(gè)索引的基數(shù)越大浓瞪,意味著走索引查詢越有優(yōu)勢懈玻。

那么問題來了,怎么知道這個(gè)索引的基數(shù)呢乾颁?

系統(tǒng)當(dāng)然是不會遍歷全部來獲得一個(gè)索引的基數(shù)的涂乌,代價(jià)太大了艺栈,索引系統(tǒng)是通過遍歷部分?jǐn)?shù)據(jù),也就是通過采樣的方式骂倘,來預(yù)測索引的基數(shù)的眼滤。

扯了這么多,重點(diǎn)的來了历涝,居然是采樣诅需,那就有可能出現(xiàn)失誤的情況,也就是說荧库,c 這個(gè)索引的基數(shù)實(shí)際上是很大的堰塌,但是采樣的時(shí)候,卻很不幸分衫,把這個(gè)索引的基數(shù)預(yù)測成很小场刑。例如你采樣的那一部分?jǐn)?shù)據(jù)剛好基數(shù)很小,然后就誤以為索引的基數(shù)很小蚪战。然后就呵呵牵现,系統(tǒng)就不走 c 索引了,直接走全部掃描了邀桑。

所以呢瞎疼,說了這么多,得出結(jié)論:由于統(tǒng)計(jì)的失誤壁畸,導(dǎo)致系統(tǒng)沒有走索引贼急,而是走了全表掃描,而這捏萍,也是導(dǎo)致我們 SQL 語句執(zhí)行的很慢的原因太抓。

這里我聲明一下,系統(tǒng)判斷是否走索引令杈,掃描行數(shù)的預(yù)測其實(shí)只是原因之一走敌,這條查詢語句是否需要使用使用臨時(shí)表、是否需要排序等也是會影響系統(tǒng)的選擇的逗噩。

不過呢掉丽,我們有時(shí)候也可以通過強(qiáng)制走索引的方式來查詢,例如

select * from t force index(a) where c < 100 and c < 100000;

我們也可以通過

show index from t;

來查詢索引的基數(shù)和實(shí)際是否符合给赞,如果和實(shí)際很不符合的話机打,我們可以重新來統(tǒng)計(jì)索引的基數(shù),可以用這條命令

analyze table t;

來重新統(tǒng)計(jì)分析片迅。

既然會預(yù)測錯索引的基數(shù)残邀,這也意味著,當(dāng)我們的查詢語句有多個(gè)索引的時(shí)候,系統(tǒng)有可能也會選錯索引哦芥挣,這也可能是 SQL 執(zhí)行的很慢的一個(gè)原因驱闷。

好吧,就先扯這么多了空免,你到時(shí)候能扯出這么多空另,我覺得已經(jīng)很棒了,下面做一個(gè)總結(jié)蹋砚。
四扼菠、總結(jié)

以上是我的總結(jié)與理解,最后一個(gè)部分坝咐,我怕很多人不大懂?dāng)?shù)據(jù)庫居然會選錯索引循榆,所以我詳細(xì)解釋了一下,下面我對以上做一個(gè)總結(jié)墨坚。

一個(gè) SQL 執(zhí)行的很慢秧饮,我們要分兩種情況討論:

1、大多數(shù)情況下很正常泽篮,偶爾很慢盗尸,則有如下原因

(1)、數(shù)據(jù)庫在刷新臟頁帽撑,例如 redo log 寫滿了需要同步到磁盤泼各。

(2)、執(zhí)行的時(shí)候油狂,遇到鎖历恐,如表鎖寸癌、行鎖专筷。

2、這條 SQL 語句一直執(zhí)行的很慢蒸苇,則有如下原因磷蛹。

(1)、沒有用上索引:例如該字段沒有索引溪烤;由于對字段進(jìn)行運(yùn)算味咳、函數(shù)操作導(dǎo)致無法用索引。

(2)檬嘀、數(shù)據(jù)庫選錯了索引槽驶。

最后,一張無敵腦圖祭出鸳兽,腦圖在手掂铐,天下我有!


image.png

此篇博文轉(zhuǎn)載自我非常喜愛的鈣哥JavaGuide,請大家多多pick他 https://mp.weixin.qq.com/s?__biz=Mzg2OTA0Njk0OA==&mid=2247485185&idx=1&sn=66ef08b4ab6af5757792223a83fc0d45&chksm=cea248caf9d5c1dc72ec8a281ec16aa3ec3e8066dbb252e27362438a26c33fbe842b0e0adf47&token=79317275&lang=zh_CN#rd

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末全陨,一起剝皮案震驚了整個(gè)濱河市爆班,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辱姨,老刑警劉巖柿菩,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異雨涛,居然都是意外死亡枢舶,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進(jìn)店門替久,熙熙樓的掌柜王于貴愁眉苦臉地迎上來祟辟,“玉大人,你說我怎么就攤上這事侣肄【衫В” “怎么了?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵稼锅,是天一觀的道長吼具。 經(jīng)常有香客問我,道長矩距,這世上最難降的妖魔是什么拗盒? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮锥债,結(jié)果婚禮上陡蝇,老公的妹妹穿的比我還像新娘。我一直安慰自己哮肚,他們只是感情好登夫,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著允趟,像睡著了一般恼策。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上潮剪,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天涣楷,我揣著相機(jī)與錄音,去河邊找鬼抗碰。 笑死狮斗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的弧蝇。 我是一名探鬼主播碳褒,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼迄汛,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了骤视?” 一聲冷哼從身側(cè)響起鞍爱,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎专酗,沒想到半個(gè)月后睹逃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡祷肯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年沉填,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片佑笋。...
    茶點(diǎn)故事閱讀 40,435評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡翼闹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蒋纬,到底是詐尸還是另有隱情猎荠,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布蜀备,位于F島的核電站关摇,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏碾阁。R本人自食惡果不足惜输虱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脂凶。 院中可真熱鬧宪睹,春花似錦、人聲如沸蚕钦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽冠桃。三九已至命贴,卻和暖如春道宅,著一層夾襖步出監(jiān)牢的瞬間食听,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工污茵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留樱报,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓泞当,卻偏偏與公主長得像迹蛤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,442評論 2 359