禿頂總結MySQL 最全性能優化方式

說實話,這個問題能夠涉及到 MySQL 的不少核心知識,能夠扯出一大堆,就像要考你計算機網絡的知識時,問你「輸入URL回車以後,究竟發生了什麼」同樣,看看你能說出多少了。mysql

以前騰訊面試的實話,也問到這個問題了,不過答的很很差,以前沒去想過相關緣由,致使一時之間扯不出來。因此今天,我帶你們來詳細扯一下有哪些緣由,相信你看完以後必定會有所收穫,否則你打我。面試

1、設置索引
索引是一種可讓SELECT語句提升效率的數據結構,能夠起到快速定位的做用。sql

索引的優缺點:
優勢:某些狀況下使用select語句大幅度提升效率,合適的索引能夠優化MySQL服務器的查詢性能,從而起到優化MySQL的做用。數據庫

缺點:錶行數據的變化(index、update、delect),簡歷在表列上的索引也會自動維護,必定程度上會使DML操做變慢。索引還會佔用磁盤額外的存儲空間。服務器

MySQL索引操做:
給表列建立索引:網絡

  • 建表時建立索引:
  • create table t(id int,name varchar(20),index idx_name (name));
  • 給表追加索引:
  • alter table t add unique index idx_id(id);
  • 給表的多列上追加索引
  • alter table t add index idx_id_name(id,name);

或者:數據結構

  • create index idx_id_name on t(id,name);

查看索引函數

  • 使用show語句查看t表上的索引:
  • show index from t;

或者:性能

  • show keys from t;–mysql中索引也被稱做keys

  • 使用show create table語句查看索引:
  • show create table tG

刪除索引:優化

  • 使用alter table命令刪除索引:
  • alter table 表 drop index 索引名
  • 使用drop index命令刪除索引:
  • drop index 索引名 on 表

索引原理:
例如一個學生信息表,咱們設置學號(stu_id)爲索引:

索引頁之間存在必定的關聯關係,通常爲樹形結構;分爲根節點、分支節點、和葉子節點
根節點頁中存放分段stu_id的起始值,以及值所對應的分支索引頁號
分支索引頁中存放分段stu_id的起始值,以及值所對應的葉子索引頁號
葉子索引頁中存放排序後的stu_id值,該值所對應的表頁號, 下一個葉子索引頁的頁號

stu_id創建索引後,執行select name,sex,height from stu where stu_id=13查詢過程以下:

  1. 索引頁存在關聯關係,先找索引頁號20的根節點,13在>=11和<17的範圍內,須要查找25號索引頁
  2. 讀取25號索引頁,13在>=11和<14範圍內,獲得了26號葉子索引頁
  3. 讀取26號葉子索引頁,找到了13這個值,以及該值所對應表頁的頁號161,目前只獲得了stu_id的值,還要獲得name,sex,height等,所以須要再讀一次編號爲161的表頁,裏面存放了stu_id以外的值。
  4. 讀取161號表頁,得到sname,sex,height等值

以上4步,只讀取了3個索引頁1個表頁,共4個頁,比讀取全部表頁(5000個頁),按照stu_id=13挨個翻一遍效率要高,這也是有些狀況下索引能夠加速查詢的緣由。

2、開始裝逼:分類討論

一條 SQL 語句執行的很慢,那是每次執行都很慢呢?仍是大多數狀況下是正常的,偶爾出現很慢呢?因此我以爲,咱們還得分如下兩種狀況來討論。

一、大多數狀況是正常的,只是偶爾會出現很慢的狀況。

二、在數據量不變的狀況下,這條SQL語句一直以來都執行的很慢。

針對這兩種狀況,咱們來分析下多是哪些緣由致使的。

3、針對偶爾很慢的狀況

一條 SQL 大多數狀況正常,偶爾才能出現很慢的狀況,針對這種狀況,我以爲這條SQL語句的書寫自己是沒什麼問題的,而是其餘緣由致使的,那會是什麼緣由呢?

一、數據庫在刷新髒頁(flush)我也無奈啊

當咱們要往數據庫插入一條數據、或者要更新一條數據的時候,咱們知道數據庫會在內存中把對應字段的數據更新了,可是更新以後,這些更新的字段並不會立刻同步持久化到磁盤中去,而是把這些更新的記錄寫入到 redo log 日記中去,等到空閒的時候,在經過 redo log 裏的日記把最新的數據同步到磁盤中去。

當內存數據頁跟磁盤數據頁內容不一致的時候,咱們稱這個內存頁爲「髒頁」。內存數據寫入到磁盤後,內存和磁盤上的數據頁的內容就一致了,稱爲「乾淨頁」。

刷髒頁有下面4種場景(後兩種不用太關注「性能」問題):

  • redolog寫滿了:redo log 裏的容量是有限的,若是數據庫一直很忙,更新又很頻繁,這個時候 redo log 很快就會被寫滿了,這個時候就沒辦法等到空閒的時候再把數據同步到磁盤的,只能暫停其餘操做,全身心來把數據同步到磁盤中去的,而這個時候,就會致使咱們平時正常的SQL語句忽然執行的很慢,因此說,數據庫在在同步數據到磁盤的時候,就有可能致使咱們的SQL語句執行的很慢了。
  • 內存不夠用了:若是一次查詢較多的數據,剛好碰到所查數據頁不在內存中時,須要申請內存,而此時剛好內存不足的時候就須要淘汰一部份內存數據頁,若是是乾淨頁,就直接釋放,若是剛好是髒頁就須要刷髒頁。
  • MySQL 認爲系統「空閒」的時候:這時系統沒什麼壓力。
  • MySQL 正常關閉的時候:這時候,MySQL 會把內存的髒頁都 flush 到磁盤上,這樣下次 MySQL 啓動的時候,就能夠直接從磁盤上讀數據,啓動速度會很快。

二、拿不到鎖我能怎麼辦

這個就比較容易想到了,咱們要執行的這條語句,恰好這條語句涉及到的,別人在用,而且加鎖了,咱們拿不到鎖,只能慢慢等待別人釋放鎖了。或者,表沒有加鎖,但要使用到的某個一行被加鎖了,這個時候,我也沒辦法啊。

若是要判斷是否真的在等待鎖,咱們能夠用 show processlist這個命令來查看當前的狀態哦,這裏我要提醒一下,有些命令最好記錄一下,反正,我被問了好幾個命令,都不知道怎麼寫,呵呵。

下來咱們來訪分析下第二種狀況,我以爲第二種狀況的分析纔是最重要的

4、針對一直都這麼慢的狀況

若是在數據量同樣大的狀況下,這條 SQL 語句每次都執行的這麼慢,那就就要好好考慮下你的 SQL 書寫了,下面咱們來分析下哪些緣由會致使咱們的 SQL 語句執行的很不理想。

咱們先來假設咱們有一個表,表裏有下面兩個字段,分別是主鍵 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;

(1)、字段沒有索引

恰好你的 c 字段上沒有索引,那麼抱歉,只能走全表掃描了,你就體驗不會索引帶來的樂趣了,因此,這回致使這條查詢語句很慢。

(2)、字段有索引,但卻沒有用索引

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

select * from t where c - 1 = 1000;

我想問你們一個問題,這樣子在查詢的時候會用索引查詢嗎?

答是不會,若是咱們在字段的左邊作了運算,那麼很抱歉,在查詢的時候,就不會用上索引了,因此呢,你們要注意這種字段上有索引,但因爲本身的疏忽,致使系統沒有使用索引的狀況了。

正確的查詢應該以下

select * from t where c = 1000 + 1;

有人可能會說,右邊有運算就能用上索引?難道數據庫就不會自動幫咱們優化一下,自動把 c - 1=1000 自動轉換爲 c = 1000+1。

很差意思,確實不會幫你,因此,你要注意了。

(3)、函數操做致使沒有用上索引

若是咱們在查詢的時候,對字段進行了函數操做,也是會致使沒有用上索引的,例如

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

這裏我只是作一個例子,假設函數 pow 是求 c 的 n 次方,實際上可能並無 pow(c,2)這個函數。其實這個和上面在左邊作運算也是很相似的。

因此呢,一條語句執行都很慢的時候,多是該語句沒有用上索引了,不過具體是啥緣由致使沒有用上索引的呢,你就要會分析了,我上面列舉的三個緣由,應該是出現的比較多的吧。

二、呵呵,數據庫本身選錯索引了

咱們在進行查詢操做的時候,例如

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

咱們知道,主鍵索引和非主鍵索引是有區別的,主鍵索引存放的值是整行字段的數據,而非主鍵索引上存放的值不是整行字段的數據,並且存放主鍵字段的值。  裏面有說到主鍵索引和非主鍵索引的區別

不大懂的能夠看這思惟導圖

也就是說,咱們若是走 c 這個字段的索引的話,最後會查詢到對應主鍵的值,而後,再根據主鍵的值走主鍵索引,查詢到整行數據返回。

好吧扯了這麼多,其實我就是想告訴你,就算你在 c 字段上有索引,系統也並不必定會走 c 這個字段上的索引,而是有可能會直接掃描掃描全表,找出全部符合 100 < c and c < 100000 的數據。

爲何會這樣呢?

實際上是這樣的,系統在執行這條語句的時候,會進行預測:到底是走 c 索引掃描的行數少,仍是直接掃描全表掃描的行數少呢?顯然,掃描行數越少固然越好了,由於掃描行數越少,意味着I/O操做的次數越少。

若是是掃描全表的話,那麼掃描的次數就是這個表的總行數了,假設爲 n;而若是走索引 c 的話,咱們經過索引 c 找到主鍵以後,還得再經過主鍵索引來找咱們整行的數據,也就是說,須要走兩次索引。並且,咱們也不知道符合 100 c < and c < 10000 這個條件的數據有多少行,萬一這個表是所有數據都符合呢?這個時候意味着,走 c 索引不只掃描的行數是 n,同時還得每行數據走兩次索引。

因此呢,系統是有可能走全表掃描而不走索引的。那系統是怎麼判斷呢?

判斷來源於系統的預測,也就是說,若是要走 c 字段索引的話,系統會預測走 c 字段索引大概須要掃描多少行。若是預測到要掃描的行數不少,它可能就不走索引而直接掃描全表了。

那麼問題來了,系統是怎麼預測判斷的呢?這裏我給你講下系統是怎麼判斷的吧,雖然這個時候我已經寫到脖子有點酸了。

系統是經過索引的區分度來判斷的,一個索引上不一樣的值越多,意味着出現相同數值的索引越少,意味着索引的區分度越高。咱們也把區分度稱之爲基數,即區分度越高,基數越大。因此呢,基數越大,意味着符合 100 < c and c < 10000 這個條件的行數越少。

因此呢,一個索引的基數越大,意味着走索引查詢越有優點。

那麼問題來了,怎麼知道這個索引的基數呢?

系統固然是不會遍歷所有來得到一個索引的基數的,代價太大了,索引系統是經過遍歷部分數據,也就是經過採樣的方式,來預測索引的基數的。

扯了這麼多,重點的來了,竟然是採樣,那就有可能出現失誤的狀況,也就是說,c 這個索引的基數其實是很大的,可是採樣的時候,卻很不幸,把這個索引的基數預測成很小。例如你採樣的那一部分數據恰好基數很小,而後就誤覺得索引的基數很小。而後就呵呵,系統就不走 c 索引了,直接走所有掃描了

因此呢,說了這麼多,得出結論:因爲統計的失誤,致使系統沒有走索引,而是走了全表掃描,而這,也是致使咱們 SQL 語句執行的很慢的緣由。

這裏我聲明一下,系統判斷是否走索引,掃描行數的預測其實只是緣由之一,這條查詢語句是否須要使用使用臨時表、是否須要排序等也是會影響系統的選擇的。

不過呢,咱們有時候也能夠經過強制走索引的方式來查詢,例如

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

咱們也能夠經過

show index from t;

來查詢索引的基數和實際是否符合,若是和實際很不符合的話,咱們能夠從新來統計索引的基數,能夠用這條命令

analyze table t;

來從新統計分析。

既然會預測錯索引的基數,這也意味着,當咱們的查詢語句有多個索引的時候,系統有可能也會選錯索引哦,這也多是 SQL 執行的很慢的一個緣由。

好吧,就先扯這麼多了,你到時候能扯出這麼多,我以爲已經很棒了,下面作一個總結。

5、總結

以上是個人總結與理解,最後一個部分,我怕不少人不大懂數據庫竟然會選錯索引,因此我詳細解釋了一下,下面我對以上作一個總結。

一個 SQL 執行的很慢,咱們要分兩種狀況討論:

一、大多數狀況下很正常,偶爾很慢,則有以下緣由

(1)、數據庫在刷新髒頁,例如 redo log 寫滿了須要同步到磁盤。

(2)、執行的時候,遇到鎖,如表鎖、行鎖。

二、這條 SQL 語句一直執行的很慢,則有以下緣由。

(1)、沒有用上索引:例如該字段沒有索引;因爲對字段進行運算、函數操做致使沒法用索引。

(2)、數據庫選錯了索引。

相關文章
相關標籤/搜索