微盟面試:為什么一條SQL語(yǔ)句執(zhí)行很慢?

??????筆者在之前的一次面試經(jīng)歷中,被面試官問(wèn)到這一問(wèn)題,當(dāng)時(shí)答得不是很好,后來(lái)也查看了很多資料,今天做一個(gè)詳細(xì)的總結(jié)。

??????在我們的日常開(kāi)發(fā)中,肯定或多或少的遇到過(guò) SQL 語(yǔ)句查詢慢的問(wèn)題,遇到這種問(wèn)題無(wú)非就兩種情況:一、大部分情況下都是執(zhí)行的很快,偶爾需要很長(zhǎng)時(shí)間。二、SQL 語(yǔ)句一直都執(zhí)行得很慢。接下來(lái)將對(duì)這兩種情況進(jìn)行詳細(xì)分析。

一、偶爾執(zhí)行很慢

??????大部分情況下執(zhí)行都是正常得,部分時(shí)間執(zhí)行很慢,首先可能確認(rèn) SQL 語(yǔ)句本身是沒(méi)有多大的問(wèn)題的,那是什么原因呢?

1、數(shù)據(jù)庫(kù)在刷新臟頁(yè)

??????臟頁(yè) 是指當(dāng)內(nèi)存中的數(shù)據(jù)頁(yè)和磁盤數(shù)據(jù)頁(yè)中的不一致時(shí),我們把內(nèi)存數(shù)據(jù)頁(yè)稱作“臟頁(yè)”;否則稱之為“干凈頁(yè)”。
??????當(dāng)我們對(duì)數(shù)據(jù)庫(kù)進(jìn)行更新操作(增、刪、改等)時(shí),數(shù)據(jù)庫(kù)首先會(huì)在內(nèi)存中對(duì)數(shù)據(jù)進(jìn)行更新,但這并不會(huì)直接同步到磁盤中,而是把這些更新的記錄寫道 redo log 日志中去,然后等系統(tǒng)空閑時(shí)再寫到磁盤中。

刷新臟頁(yè)的情況:

  • redo log寫滿了,和其他容器一樣,redo log 的容量也是有限的,當(dāng)我們一直對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作,redo log 很快就會(huì)被寫滿,這時(shí)數(shù)據(jù)庫(kù)就不能等到系統(tǒng)空閑時(shí)刷新臟頁(yè)了,就只能暫停其他操作,盡快把數(shù)據(jù)同步到磁盤當(dāng)中,因此,有時(shí)候我們的 SQL 語(yǔ)句執(zhí)行的會(huì)比較慢的情況。
  • 內(nèi)存不夠用了,上面介紹了,數(shù)據(jù)庫(kù)會(huì)在內(nèi)存中進(jìn)行操作,當(dāng)我們一次性查詢很多數(shù)據(jù),而這些數(shù)據(jù)都不在內(nèi)存當(dāng)中,更可惡的是此時(shí)的內(nèi)存不夠用了,這時(shí)候就需要釋放一部分內(nèi)存數(shù)據(jù)頁(yè),如果這些數(shù)據(jù)頁(yè)都是臟頁(yè),那么將刷新臟頁(yè);如果是干凈頁(yè)的話直接釋放就好了。
  • MySQL 認(rèn)為系統(tǒng)空閑,這時(shí)候就會(huì)刷新臟頁(yè)了。
  • MySQL 正常關(guān)閉,當(dāng)數(shù)據(jù)庫(kù)關(guān)閉的時(shí)候,會(huì)把臟頁(yè)刷新到磁盤中,在下一次啟動(dòng)時(shí),直接從磁盤中讀取數(shù)據(jù)。

2、拿不到鎖

??????這個(gè)應(yīng)該很好理解吧,數(shù)據(jù)庫(kù)有各種鎖,當(dāng)我們執(zhí)行一條語(yǔ)句的時(shí)候,可能會(huì)需要某種鎖,但此時(shí)有其他用戶在使用這個(gè)鎖,那很無(wú)奈啊,只能等別人用完釋放鎖了。

二、一直執(zhí)行很慢

??????在數(shù)據(jù)量一定的情況下,如果你的 SQL 語(yǔ)句執(zhí)行的很慢,你就要好好想想你的 SQL 語(yǔ)句了。
??????首先我們建如下的表,為后續(xù)的說(shuō)明打基礎(chǔ)。

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

1、沒(méi)有用到索引

 SELECT * FROM `test` WHERE `c` < 10000;
 SELECT * FROM `test` WHERE `id`-1 = 100;

??????以上的這兩種情況都不會(huì)用到索引,第一個(gè)是因?yàn)樽侄?code>c本身沒(méi)有索引,第二是在索引列做了運(yùn)算操作。至于其他的一些會(huì)造成索引失效的情況,在這里就不詳細(xì)介紹了。因?yàn)榻酉聛?lái)的內(nèi)容才是重點(diǎn)。

2、數(shù)據(jù)庫(kù)自己選錯(cuò)了索引

??????數(shù)據(jù)庫(kù)在執(zhí)行語(yǔ)句的時(shí)候,會(huì)進(jìn)行一個(gè)預(yù)測(cè):走索引掃描的行數(shù)少還是直接全表掃描的行數(shù)少?掃描行數(shù)越少也好,掃描行數(shù)越少,I/O操作的次數(shù)也就越少。
??????主鍵索引存放的值是整行字段的數(shù)據(jù),而非主鍵索引上存放的值不是整段的數(shù)據(jù),而是主鍵字段的值。如果查詢走非主鍵字段,會(huì)先查詢對(duì)應(yīng)主鍵的值,然后再走一遍主鍵索引,然后查詢到整行數(shù)據(jù)。

系統(tǒng)怎么預(yù)測(cè)判斷呢?

??????系統(tǒng)是通過(guò)索引的\color{#FF3030}{區(qū)分度}來(lái)判斷的,一個(gè)索引上不同的值越多,意味著出現(xiàn)相同數(shù)值的索引越少,意味著索引的區(qū)分度越高。我們也把區(qū)分度稱之為 基數(shù) ,即 區(qū)分度越高,基數(shù)越大。

??????系統(tǒng)怎么獲取這個(gè)索引的基數(shù)呢? 通過(guò)采樣的辦法,遍歷部分?jǐn)?shù)據(jù)。采樣的方式可能會(huì)出現(xiàn)失誤,一個(gè)索引的基數(shù)實(shí)際上可能很大,但在采樣的過(guò)程中,很不幸,數(shù)據(jù)庫(kù)遍歷了那份基數(shù)很小的。然后就直接全表掃描了。
?
?


??????未完,待續(xù)!


?
?
?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容