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分析,索引建立的規則等等。。
下章為大家講解:如何正確建立索引,最左匹配法,建立索引需要注意什麼,如何避免索引失效等等……