SQL:我爲何慢你內心沒數嗎?

SQL 語句執行慢的緣由是面試中常常會被問到的,對於服務端開發來講也是必需要關注的問題。mysql

在生產環境中,SQL 執行慢是很嚴重的事件。那麼如何定位慢 SQL、慢的緣由及如何防患於未然。接下來帶着這些問題讓咱們開啓本期之旅!面試

圖注:思惟導圖sql

寫操做 

做爲後端開發,平常操做數據庫最經常使用的是寫操做和讀操做。讀操做咱們下邊會講,這個分類裏咱們主要來看看寫操做時爲何會致使 SQL 變慢。數據庫

刷髒頁

髒頁的定義是這樣的:內存數據頁和磁盤數據頁不一致時,那麼稱這個內存數據頁爲髒頁。後端

那爲何會出現髒頁,刷髒頁又怎麼會致使 SQL 變慢呢?那就須要咱們來看看寫操做時的流程是什麼樣的。緩存

對於一條寫操做的 SQL 來講,執行的過程當中涉及到寫日誌,內存及同步磁盤這幾種狀況。服務器

 

圖注:Mysql 架構圖架構

這裏要提到一個日誌文件,那就是 redo log,位於存儲引擎層,用來存儲物理日誌。在寫操做的時候,存儲引擎(這裏討論的是 Innodb)會將記錄寫入到 redo log 中,並更新緩存,這樣更新操做就算完成了。後續操做存儲引擎會在適當的時候把操做記錄同步到磁盤裏。併發

看到這裏你可能會有個疑問,redo log 不是日誌文件嗎,日誌文件就存儲在磁盤上,那寫的時候豈不很慢嗎?高併發

其實,寫redo log 的過程是順序寫磁盤的,磁盤順序寫減小了尋道等時間,速度比隨機寫要快不少( 相似Kafka存儲原理),所以寫 redo log 速度是很快的。

好了,讓咱們回到開始時候的問題,爲何會出現髒頁,而且髒頁爲何會使 SQL 變慢。你想一想,redo log 大小是必定的,且是循環寫入的。在高併發場景下,redo log 很快被寫滿了,可是數據來不及同步到磁盤裏,這時候就會產生髒頁,而且還會阻塞後續的寫入操做。SQL 執行天然會變慢。

寫操做時 SQL 慢的另外一種狀況是可能遇到了鎖,這個很容易理解。舉個例子,你和別人合租了一間屋子,只有一個衛生間,大家倆同時都想去,但對方比你早了一丟丟。那麼此時你只能等對方出來後才能進去。

對應到 Mysql 中,當某一條 SQL 所要更改的行恰好被加了鎖,那麼此時只有等鎖釋放了後才能進行後續操做。

可是還有一種極端狀況,你的室友一直佔用着衛生間,那麼此時你該怎麼整,總不能尿褲子吧,多丟人。對應到Mysql 裏就是遇到了死鎖或是鎖等待的狀況。這時候該如何處理呢?

Mysql 中提供了查看當前鎖狀況的方式:

 經過在命令行執行圖中的語句,能夠查看當前運行的事務狀況,這裏介紹幾個查詢結果中重要的參數:

當前事務若是等待時間過長或出現死鎖的狀況,能夠經過 「kill 線程ID」 的方式釋放當前的鎖。

這裏的線程 ID 指表中 trx_mysql_thread_id 參數。

讀操做

說完了寫操做,讀操做你們可能相對來講更熟悉一些。SQL 慢致使讀操做變慢的問題在工做中是常常會被涉及到的。

慢查詢

在講讀操做變慢的緣由以前咱們先來看看是如何定位慢 SQL 的。Mysql 中有一個叫做慢查詢日誌的東西,它是用來記錄超過指定時間的 SQL 語句的。默認狀況下是關閉的,經過手動配置才能開啓慢查詢日誌進行定位。

具體的配置方式是這樣的:

  • 查看當前慢查詢日誌的開啓狀況:

  • 開啓慢查詢日誌(臨時):

注意這裏只是臨時開啓了慢查詢日誌,若是 mysql 重啓後則會失效。能夠 my.cnf 中進行配置使其永久生效。

存在緣由

知道了如何查看執行慢的 SQL 了,那麼咱們接着看讀操做時爲何會致使慢查詢。

(1)未命中索引

SQL 查詢慢的緣由之一是可能未命中索引,關於使用索引爲何能使查詢變快以及使用時的注意事項,網上已經不少了,這裏就很少贅述了。

(2)髒頁問題

另外一種仍是咱們上邊所提到的刷髒頁狀況,只不過和寫操做不一樣的是,是在讀時候進行刷髒頁的。

是否是有點懵逼,別急,聽我娓娓道來:

爲了不每次在讀寫數據時訪問磁盤增長 IO 開銷,Innodb 存儲引擎經過把相應的數據頁和索引頁加載到內存的緩衝池(buffer pool)中來提升讀寫速度。而後按照最近最少使用原則來保留緩衝池中的緩存數據。

那麼當要讀入的數據頁不在內存中時,就須要到緩衝池中申請一個數據頁,但緩衝池中數據頁是必定的,當數據頁達到上限時此時就須要把最久不使用的數據頁從內存中淘汰掉。但若是淘汰的是髒頁呢,那麼就須要把髒頁刷到磁盤裏才能進行復用。

你看,又回到了刷髒頁的狀況,讀操做時變慢你也能理解了吧?

防患於未然

知道了緣由,咱們如何來避免或緩解這種狀況呢?

首先來看未命中索引的狀況:

不知道你們有沒有使用 Mysql 中 explain 的習慣,反正我是每次都會用它來查看下當前 SQL 命中索引的狀況。避免其帶來一些未知的隱患。

這裏簡單介紹下其使用方式,經過在所執行的 SQL 前加上 explain 就能夠來分析當前 SQL 的執行計劃:

執行後的結果對應的字段概要描述以下圖所示:

這裏須要重點關注如下幾個字段: 

一、type

表示 MySQL 在表中找到所需行的方式。其中經常使用的類型有:ALL、index、range、 ref、eq_ref、const、system、NULL 這些類型從左到右,性能逐漸變好。

  • ALL:Mysql 遍歷全表來找到匹配的行; 

  • index:與 ALL 區別爲 index 類型只遍歷索引樹; 

  • range:只檢索給定範圍的行,使用一個索引來選擇行; 

  • ref:表示上述表的鏈接匹配條件,哪些列或常量被用於查找索引列上的值; 

  • eq_ref:相似ref,區別在於使用的是否爲惟一索引。對於每一個索引鍵值,表中只有一條記錄匹配,簡單來講,就是多表鏈接中使用 primary key 或者 unique key做爲關聯條件; 

  • const、system:當 Mysql 對查詢某部分進行優化,並轉換爲一個常量時,使用這些類型訪問。如將主鍵置於 where 列表中,Mysql 就能將該查詢轉換爲一個常量,system 是 const類型的特例,當查詢的表只有一行的狀況下,使用system; 

  • NULL:Mysql 在優化過程當中分解語句,執行時甚至不用訪問表或索引,例如從一個索引列裏選取最小值能夠經過單獨索引查找完成。

二、possible_keys 

查詢時可能使用到的索引(但不必定會被使用,沒有任何索引時顯示爲 NULL)。

三、key

實際使用到的索引。

四、rows

估算查找到對應的記錄所須要的行數。

五、Extra

比較常見的是下面幾種: 

  • Useing index:代表使用了覆蓋索引,無需進行回表;

  • Using where:不用讀取表中全部信息,僅經過索引就能夠獲取所需數據,這發生在對錶的所有的請求列都是同一個索引的部分的時候,表示mysql服務器將在存儲引擎檢索行後再進行過濾; 

  • Using temporary:表示MySQL須要使用臨時表來存儲結果集,常見於排序和分組查詢,常見 group by,order by;

  • Using filesort:當Query中包含 order by 操做,並且沒法利用索引完成的排序操做稱爲「文件排序」。 

對於刷髒頁的狀況,咱們須要控制髒頁的比例,不要讓它常常接近 75%。同時還要控制 redo log 的寫盤速度,而且經過設置 innodb_io_capacity 參數告訴 InnoDB 你的磁盤能力。

總結

寫操做 

  • 當 redo log 寫滿時就會進行刷髒頁,此時寫操做也會終止,那麼 SQL 執行天然就會變慢。 

  • 遇到所要修改的數據行或表加了鎖時,須要等待鎖釋放後才能進行後續操做,SQL 執行也會變慢。

讀操做

  • 讀操做慢很常見的緣由是未命中索引從而致使全表掃描,能夠經過 explain 方式對 SQL 語句進行分析。 
  • 另外一種緣由是在讀操做時,要讀入的數據頁不在內存中,須要經過淘汰髒頁才能申請新的數據頁從而致使執行變慢。

關於做者

做者:你們好,我是萊烏,BAT搬磚工一枚。從小公司進入大廠,一路走來收穫良多,想將這些經驗分享給有須要的人,所以建立了公衆號【IT界農民工】。定時更新,但願能幫助到你。在公衆號內回覆【pdf】可免費獲取一份Redis面經手冊。

相關文章
相關標籤/搜索