正文
作為後端開發,日常操作資料庫最常用的是寫操作和讀操作。讀操作我們下邊會講,這個分類裡我們主要來看看寫操作時為什麼會導緻 SQL 變慢。
刷髒頁
髒頁的定義是這樣的:記憶體資料頁和磁盤資料頁不一緻時,那麼稱這個記憶體資料頁為髒頁。
那為什麼會出現髒頁,刷髒頁又怎麼會導緻 SQL 變慢呢?那就需要我們來看看寫操作時的流程是什麼樣的。
對于一條寫操作的 SQL 來說,執行的過程中涉及到寫日志,記憶體及同步磁盤這幾種情況。

這裡要提到一個日志檔案,那就是 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 的執行計劃:
執行後的結果對應的字段概要描述如下圖所示:
這裡需要重點關注以下幾個字段:
1、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 在優化過程中分解語句,執行時甚至不用通路表或索引,例如從一個索引列裡選取最小值可以通過單獨索引查找完成。
2、possible_keys
查詢時可能使用到的索引(但不一定會被使用,沒有任何索引時顯示為 NULL)。
3、key
實際使用到的索引。
4、rows
估算查找到對應的記錄所需要的行數。
5、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 你的磁盤能力。
最後
提供一下免費的Java架構學習資料給大家,學習技術内容包含有:Spring,Dubbo,MyBatis, RPC, 源碼分析,高并發、高性能、分布式,性能優化,微服務 進階架構開發等等。
需要的朋友可以戳這裡免費領取
還有Java核心知識點+全套架構師學習資料和視訊+一線大廠面試寶典+面試履歷模闆可以領取+阿裡美團網易騰訊小米愛奇藝快手哔哩哔哩面試題+Spring源碼合集+Java架構實戰電子書+2021年最新大廠面試題。
以戳這裡免費領取](https://gitee.com/vip204888/java-p7)**
還有Java核心知識點+全套架構師學習資料和視訊+一線大廠面試寶典+面試履歷模闆可以領取+阿裡美團網易騰訊小米愛奇藝快手哔哩哔哩面試題+Spring源碼合集+Java架構實戰電子書+2021年最新大廠面試題。