1. 程式人生 > 資料庫 >Mysql從索引原理對SQL分析優化實戰

Mysql從索引原理對SQL分析優化實戰

無論是在大型專案,還是小型專案中,隨著業務的迭代,使用者的增長,資料庫資料往往都是成百萬級別的,這時候普通的sql語句執行起來是非常慢的,這時候就需要對sql進行優化啦,接下來將手把手從索引原理帶你學會如何分析優化,寫出一手高逼格的sql。

 

Mysql的索引儲存原理:

mysql有兩種索引:**hash**和**b+tree**


 

select * from user where age = 10 (生效)

select * from user where  age > 10 (不生效)

(如上圖)hash索引:Hash 進行檢索效率非常高,通過對key進行hash並可以找到對應的資料,但是它是**不支援範圍查詢**的,如上面的sql

 

 

(如上圖)b+tree索引:它是由一個個磁碟塊組成的。形成一顆樹,特性如下

1, 非葉子節點只儲存主鍵

2, 葉子節點儲存資料,並且資料與資料之間有指標關聯,這就是為什麼範圍查詢b+tree起到作用了。

3,需要注意的是,b+tree形成的時候就已經按照索引的順序排列了。

**疑問**:

1,為什麼b+tree要把data資料存放在葉子節點呢?

2,為什麼非葉子節點只存放主鍵呢?

解答:

1, mysql預設每一節點層是儲存16k的資料,目的是為了使非葉子節點儲存更多的索引key值,控制樹的高度。
2, 假設:儲存的主鍵索引是bigint型別,預設是8b大小,而儲存子節點的地址是6b,總共14b。16kb/14b=1170,所以根節點可以儲存1170個key,假設有三層的話,預設儲存的資料1k的話,那麼葉節點可以儲存16個數據,1170*1170*16=兩千一百多萬,兩千多萬的資料只需要三次I/O就可以找到資料。

總結:
**hash**不支援範圍查詢,時間複雜度:O(1)
**B+tree**支援範圍查詢,時間複雜度:O(log n)

一般專案中我們常用的都是B—tree的索引,因為需要範圍查詢,這個根據實際情況建立索引。

 

定位sql+分析sql

優化哪些sql:

首先,我們要對sql進行優化,那必須要找到執行慢的sql,可以通過下面的步驟設定對sql的監控

開啟慢查詢日誌:

Linux系統下是編輯/etc/my.conf

開啟慢查詢日誌:slow_query_log=ON    
慢查詢日誌記錄到的檔案路徑:slow_query_log_file=/var/lib/mysql/slow-mysql.log
執行超過多少秒為慢查詢:long_query_time=1   (超過1秒鐘視為慢查詢)

在命令視窗通過命令匯出慢sql
mysqldumpslow slow-mysql.log

 

通過explain關鍵字檢視該sql執行計劃

寫法:explain + (需要分析的sql)

 


 

type:

system    :const的特例,僅返回一條資料的時候。
const  :  查詢主鍵索引,返回的資料至多一條(0或者1條)。 屬於精確查詢
eq_ref  :  查詢唯一性索引,返回的資料至多一條。屬於精確查詢
ref   :  查詢非唯一性索引,返回匹配某一條件的多條資料。屬於精確查詢、資料返回可能是多條
range   :  查詢某個索引的部分索引,一般在where子句中使用 < 、>、in、between等關鍵詞。只檢索給定範圍的行,屬於範圍查詢
index   :  查詢所有的索引樹,比ALL要快的多,因為索引檔案要比資料檔案小的多
ALL     :  不使用任何索引,進行全表掃描,效能最差。

 

key:
表示實際使用的索引

 
rows:

掃描出的行數(估算的行數)

 
filtered:

按表條件過濾的行百分比

 

Extra:
執行情況的描述和說明

 


實戰:

假如一張表有三百多萬條資料,需要分頁查詢出billType 為’abc’ 和 status為1的第2000000後10 條記錄?

優化前的sql : select * from Bill_online where billType = 'abc' and status=1 limit 2000000,10

我們來分析一下。在沒有索引條件下分頁搜尋的原理:

mysql從 0 到 2000000 所有資料全部掃描一次,然後再取出10條資料,最後丟棄前面的資料,這樣大量浪費了時間。經過測試**全表掃描**用時:** 2.7**s

那我們上面講到,mysql的索引既然那麼快,我們不妨加個索引看看,加索引也是有規則的,如何加才能讓索引起到作用,這裡條件是billType和status,我們給他加個聯合索引(b-tree型別的,因為我們是範圍查詢)。

 

建立語句:ALTER TABLEBillADD INDEXbillType_status_index(billType,status) USING BTREE ;

索引已經建立完畢,接著跑了一下sql。如下圖,9s多,天啊

 不急不急,我們照常分析一下。

如上圖:該sql執行計劃用到索引,為什麼會如此慢呢,比全表掃描還要慢上6秒,那我們來看一下用到索引的執行原理和過程,才能判斷他為什麼那麼慢。
#####

 

以下為重點,需要耐心耐心耐心的細看哦:

如上圖,右邊為普通索引,左邊為聚集索引,聚集索引就是以主鍵作為索引,這個是預設的,並且葉子節點是存有索引對應的行資料的,稱為聚集索引,而普通索引呢,葉子節點存放的是主鍵索引的指標,而上面建立的聯合索引就是一個普通索引,這一點大家需要清楚。

那我們分析下加了索引的sql,該sql是利用我們建立的聯合索引(普通索引)去查詢資料,從1開始查,先是在右邊索引樹找到對應的主鍵id,在通過id去找對應的行資料,這裡因為返回的列是 “*” ,這些資料只有主鍵索引的樹才有,所有每次查都會從右邊開始,一直找左邊的樹,最後找到資料再返回。一直到2000000行,這裡利用所有慢的理由就是每次都要遍歷兩棵索引樹,大大浪費了IO。

那我們能不能只掃描右邊的樹呢,這樣不就可以減少IO時間了嗎。那我們看看右邊的樹有什麼資料,聯合索引的值:billType,status,id, 這三種類型資料,那我們可以先取出需要的id,再去右邊的表掃描取出需要的資料,那這樣是不是更快呢,這裡就是用到了覆蓋索引,覆蓋索引就是說當前用到的索引在該索引樹可以直接取到資料,不需要回表查詢。

優化後:

select * from Bill_online b inner join 
    (select id from Bill_online where billType = 'consume' and status=1 limit 2000000,10) a 
    on a.id = b.id

 

我們看一下上面的sql,通過子查詢把需要的id查詢出來(用到覆蓋索引),然後在拿著id去inner join需要的資料。這樣就大大減少了不必要的IO了。

 

如上圖,優化後直接是進入1秒,這差距是不是有點大,我們再來通過explain看看執行計劃。如下圖:

 

這裡其實還可以再優化下:

select * from Bill_online where id >=( select id from Bill_online where billType = 'consume' and status=1 limit 2000000,1) limit 10

 

其實原理都是一樣的,我們需要掌握索引儲存和執行原理,以及通過explain分析,索引建立的規則等等。。

下章為大家講解:如何正確建立索引,最左匹配法,建立索引需要注意什麼,如何避免索引失效等等……