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

說實話姿现,這個問題可以涉及到 MySQL 的很多核心知識,可以扯出一大堆肖抱,就像要考你計算機網(wǎng)絡(luò)的知識時备典,問你“輸入URL回車之后,究竟發(fā)生了什么”一樣意述,看看你能說出多少了提佣。

之前騰訊面試的實話,也問到這個問題了荤崇,不過答的很不好拌屏,之前沒去想過相關(guān)原因,導(dǎo)致一時之間扯不出來术荤。所以今天倚喂,我?guī)Т蠹襾碓敿毘兑幌掠心男┰颍嘈拍憧赐曛笠欢〞兴斋@瓣戚,不然你打我端圈。

開始裝逼:分類討論

一條 SQL 語句執(zhí)行的很慢,那是每次執(zhí)行都很慢呢子库?還是大多數(shù)情況下是正常的舱权,偶爾出現(xiàn)很慢呢?所以我覺得仑嗅,我們還得分以下兩種情況來討論宴倍。

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

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

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

針對偶爾很慢的情況

一條 SQL 大多數(shù)情況正常,偶爾才能出現(xiàn)很慢的情況颜价,針對這種情況涯保,我覺得這條SQL語句的書寫本身是沒什么問題的,而是其他原因?qū)е碌闹苈祝菚鞘裁丛蚰兀?/p>

數(shù)據(jù)庫在刷新臟頁我也無奈啊

當我們要往數(shù)據(jù)庫插入一條數(shù)據(jù)夕春、或者要更新一條數(shù)據(jù)的時候,我們知道數(shù)據(jù)庫會在內(nèi)存中把對應(yīng)字段的數(shù)據(jù)更新了专挪,但是更新之后及志,這些更新的字段并不會馬上同步持久化到磁盤中去,而是把這些更新的記錄寫入到 redo log 日記中去寨腔,等到空閑的時候速侈,在通過 redo log 里的日記把最新的數(shù)據(jù)同步到磁盤中去。

不過迫卢,redo log 里的容量是有限的倚搬,如果數(shù)據(jù)庫一直很忙,更新又很頻繁乾蛤,這個時候 redo log 很快就會被寫滿了每界,這個時候就沒辦法等到空閑的時候再把數(shù)據(jù)同步到磁盤的,只能暫停其他操作家卖,全身心來把數(shù)據(jù)同步到磁盤中去的眨层,而這個時候,就會導(dǎo)致我們平時正常的SQL語句突然執(zhí)行的很慢上荡,所以說趴樱,數(shù)據(jù)庫在在同步數(shù)據(jù)到磁盤的時候,就有可能導(dǎo)致我們的SQL語句執(zhí)行的很慢了酪捡。

拿不到鎖我能怎么辦

這個就比較容易想到了叁征,我們要執(zhí)行的這條語句,剛好這條語句涉及到的沛善,別人在用航揉,并且加鎖了塞祈,我們拿不到鎖金刁,只能慢慢等待別人釋放鎖了帅涂。或者尤蛮,表沒有加鎖媳友,但要使用到的某個一行被加鎖了,這個時候产捞,我也沒辦法啊醇锚。

如果要判斷是否真的在等待鎖,我們可以用show processlist這個命令來查看當前的狀態(tài)哦坯临,這里我要提醒一下焊唬,有些命令最好記錄一下,反正看靠,我被問了好幾個命令赶促,都不知道怎么寫,呵呵挟炬。

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

針對一直都這么慢的情況

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

我們先來假設(shè)我們有一個表粥喜,表里有下面兩個字段,分別是主鍵 id凸主,和兩個普通字段 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;

扎心了额湘,沒用到索引

沒有用上索引秕铛,我覺得這個原因是很多人都能想到的,例如你要查詢這條語句

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

字段沒有索引

剛好你的 c 字段上沒有索引缩挑,那么抱歉但两,只能走全表掃描了,你就體驗不會索引帶來的樂趣了供置,所以谨湘,這回導(dǎo)致這條查詢語句很慢。

字段有索引芥丧,但卻沒有用索引

好吧紧阔,這個時候你給 c 這個字段加上了索引,然后又查詢了一條語句

select * from t where c - 1 = 1000;

我想問大家一個問題续担,這樣子在查詢的時候會用索引查詢嗎擅耽?

答是不會,如果我們在字段的左邊做了運算物遇,那么很抱歉乖仇,在查詢的時候憾儒,就不會用上索引了,所以呢乃沙,大家要注意這種字段上有索引起趾,但由于自己的疏忽,導(dǎo)致系統(tǒng)沒有使用索引的情況了警儒。

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

select * from t where c = 1000 + 1;

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

不好意思,確實不會幫你记劝,所以艺骂,你要注意了。

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

如果我們在查詢的時候隆夯,對字段進行了函數(shù)操作钳恕,也是會導(dǎo)致沒有用上索引的,例如

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

這里我只是做一個例子蹄衷,假設(shè)函數(shù) pow 是求 c 的 n 次方忧额,實際上可能并沒有 pow(c,2)這個函數(shù)。其實這個和上面在左邊做運算也是很類似的愧口。

所以呢睦番,一條語句執(zhí)行都很慢的時候,可能是該語句沒有用上索引了耍属,不過具體是啥原因?qū)е聸]有用上索引的呢托嚣,你就要會分析了,我上面列舉的三個原因厚骗,應(yīng)該是出現(xiàn)的比較多的吧示启。

呵呵,數(shù)據(jù)庫自己選錯索引了

我們在進行查詢操作的時候领舰,例如

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

我們知道夫嗓,主鍵索引和非主鍵索引是有區(qū)別的,主鍵索引存放的值是整行字段的數(shù)據(jù)冲秽,而非主鍵索引上存放的值不是整行字段的數(shù)據(jù)舍咖,而且存放主鍵字段的值。不大懂的可以看我這篇文章:面試小知識:MySQL索引相關(guān)?里面有說到主鍵索引和非主鍵索引的區(qū)別

也就是說锉桑,我們?nèi)绻?c 這個字段的索引的話排霉,最后會查詢到對應(yīng)主鍵的值,然后民轴,再根據(jù)主鍵的值走主鍵索引攻柠,查詢到整行數(shù)據(jù)返回球订。

好吧扯了這么多,其實我就是想告訴你辙诞,就算你在 c 字段上有索引辙售,系統(tǒng)也并不一定會走 c 這個字段上的索引轻抱,而是有可能會直接掃描掃描全表飞涂,找出所有符合 100 < c and c < 100000 的數(shù)據(jù)。

為什么會這樣呢祈搜?

其實是這樣的较店,系統(tǒng)在執(zhí)行這條語句的時候,會進行預(yù)測:究竟是走 c 索引掃描的行數(shù)少容燕,還是直接掃描全表掃描的行數(shù)少呢梁呈?顯然,掃描行數(shù)越少當然越好了蘸秘,因為掃描行數(shù)越少官卡,意味著I/O操作的次數(shù)越少。

如果是掃描全表的話醋虏,那么掃描的次數(shù)就是這個表的總行數(shù)了寻咒,假設(shè)為 n;而如果走索引 c 的話颈嚼,我們通過索引 c 找到主鍵之后毛秘,還得再通過主鍵索引來找我們整行的數(shù)據(jù),也就是說阻课,需要走兩次索引叫挟。而且,我們也不知道符合 100 c < and c < 10000 這個條件的數(shù)據(jù)有多少行限煞,萬一這個表是全部數(shù)據(jù)都符合呢抹恳?這個時候意味著,走 c 索引不僅掃描的行數(shù)是 n署驻,同時還得每行數(shù)據(jù)走兩次索引适秩。

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

判斷來源于系統(tǒng)的預(yù)測,也就是說抚官,如果要走 c 字段索引的話扬跋,系統(tǒng)會預(yù)測走 c 字段索引大概需要掃描多少行。如果預(yù)測到要掃描的行數(shù)很多凌节,它可能就不走索引而直接掃描全表了钦听。

那么問題來了洒试,系統(tǒng)是怎么預(yù)測判斷的呢?這里我給你講下系統(tǒng)是怎么判斷的吧朴上,雖然這個時候我已經(jīng)寫到脖子有點酸了垒棋。

系統(tǒng)是通過索引的區(qū)分度來判斷的,一個索引上不同的值越多痪宰,意味著出現(xiàn)相同數(shù)值的索引越少叼架,意味著索引的區(qū)分度越高。我們也把區(qū)分度稱之為基數(shù)衣撬,即區(qū)分度越高乖订,基數(shù)越大。所以呢具练,基數(shù)越大乍构,意味著符合 100 < c and c < 10000 這個條件的行數(shù)越少。

所以呢扛点,一個索引的基數(shù)越大哥遮,意味著走索引查詢越有優(yōu)勢。

那么問題來了陵究,怎么知道這個索引的基數(shù)呢眠饮?

系統(tǒng)當然是不會遍歷全部來獲得一個索引的基數(shù)的,代價太大了畔乙,索引系統(tǒng)是通過遍歷部分數(shù)據(jù)君仆,也就是通過采樣的方式,來預(yù)測索引的基數(shù)的牲距。

扯了這么多返咱,重點的來了,居然是采樣牍鞠,那就有可能出現(xiàn)失誤的情況咖摹,也就是說,c 這個索引的基數(shù)實際上是很大的难述,但是采樣的時候萤晴,卻很不幸,把這個索引的基數(shù)預(yù)測成很小胁后。例如你采樣的那一部分數(shù)據(jù)剛好基數(shù)很小店读,然后就誤以為索引的基數(shù)很小。然后就呵呵攀芯,系統(tǒng)就不走 c 索引了屯断,直接走全部掃描了

所以呢,說了這么多殖演,得出結(jié)論:由于統(tǒng)計的失誤氧秘,導(dǎo)致系統(tǒng)沒有走索引,而是走了全表掃描趴久,而這丸相,也是導(dǎo)致我們 SQL 語句執(zhí)行的很慢的原因。

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

不過呢畦幢,我們有時候也可以通過強制走索引的方式來查詢坎吻,例如

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

我們也可以通過

show index from t;

來查詢索引的基數(shù)和實際是否符合,如果和實際很不符合的話宇葱,我們可以重新來統(tǒng)計索引的基數(shù)瘦真,可以用這條命令

analyze table t;

來重新統(tǒng)計分析。

既然會預(yù)測錯索引的基數(shù)黍瞧,這也意味著诸尽,當我們的查詢語句有多個索引的時候,系統(tǒng)有可能也會選錯索引哦印颤,這也可能是 SQL 執(zhí)行的很慢的一個原因您机。

好吧,就先扯這么多了年局,你到時候能扯出這么多际看,我覺得已經(jīng)很棒了,下面做一個總結(jié)矢否。

### 總結(jié)

以上是我的總結(jié)與理解仲闽,最后一個部分,我怕很多人不大懂數(shù)據(jù)庫居然會選錯索引僵朗,所以我詳細解釋了一下赖欣,下面我對以上做一個總結(jié)。

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

1顶吮、大多數(shù)情況下很正常,偶爾很慢粪薛,則有如下原因

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

(2)让禀、執(zhí)行的時候挑社,遇到鎖,如表鎖巡揍、行鎖痛阻。

2、這條 SQL 語句一直執(zhí)行的很慢腮敌,則有如下原因阱当。

(1)、沒有用上索引:例如該字段沒有索引糜工;由于對字段進行運算弊添、函數(shù)操作導(dǎo)致無法用索引。

(2)捌木、數(shù)據(jù)庫選錯了索引油坝。

大家如果有補充的,也是可以留言區(qū)補充一波哦刨裆。

歡迎工作一到五年的Java工程師朋友們加入Java程序員開發(fā): 721575865

群內(nèi)提供免費的Java架構(gòu)學(xué)習(xí)資料(里面有高可用澈圈、高并發(fā)、高性能及分布式帆啃、Jvm性能調(diào)優(yōu)瞬女、Spring源碼,MyBatis努潘,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構(gòu)資料)合理利用自己每一分每一秒的時間來學(xué)習(xí)提升自己诽偷,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕疯坤,使勁拼报慕,給未來的自己一個交代!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贴膘,一起剝皮案震驚了整個濱河市卖子,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌刑峡,老刑警劉巖洋闽,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異突梦,居然都是意外死亡诫舅,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進店門宫患,熙熙樓的掌柜王于貴愁眉苦臉地迎上來刊懈,“玉大人,你說我怎么就攤上這事⌒檠矗” “怎么了匾浪?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長卷哩。 經(jīng)常有香客問我蛋辈,道長,這世上最難降的妖魔是什么将谊? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任冷溶,我火速辦了婚禮,結(jié)果婚禮上尊浓,老公的妹妹穿的比我還像新娘逞频。我一直安慰自己,他們只是感情好栋齿,可當我...
    茶點故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布苗胀。 她就那樣靜靜地躺著,像睡著了一般褒颈。 火紅的嫁衣襯著肌膚如雪柒巫。 梳的紋絲不亂的頭發(fā)上励堡,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天谷丸,我揣著相機與錄音,去河邊找鬼应结。 笑死刨疼,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的鹅龄。 我是一名探鬼主播揩慕,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扮休!你這毒婦竟也來了迎卤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤玷坠,失蹤者是張志新(化名)和其女友劉穎蜗搔,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體八堡,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡樟凄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了兄渺。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缝龄。...
    茶點故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出叔壤,到底是詐尸還是另有隱情瞎饲,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布炼绘,位于F島的核電站企软,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏饭望。R本人自食惡果不足惜仗哨,卻給世界環(huán)境...
    茶點故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铅辞。 院中可真熱鬧厌漂,春花似錦、人聲如沸斟珊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽囤踩。三九已至旨椒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間堵漱,已是汗流浹背综慎。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留勤庐,地道東北人示惊。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像愉镰,于是被迫代替她去往敵國和親米罚。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,440評論 2 359

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