一、開始裝逼:分類討論
一條 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ù)庫選錯了索引槽驶。
最后,一張無敵腦圖祭出鸳兽,腦圖在手掂铐,天下我有!
此篇博文轉(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