目錄
1. InnoDB表的索引的幾個概念
2. 覆蓋索引和回表
3. 分頁查詢
4. 延遲關聯優化
寫在前面
下面的介紹均是在選用MySQL數據庫和Innodb引擎的基礎開展。我們先來學習索引的幾個概念,幫助我們理解延遲關聯優化的加快分頁查詢速度的原因。
一、Innodb表的索引的幾個概念
InnoDB表是基於聚簇索引建立的。
索引一般分為主鍵索引和普通索引(輔助索引),聚簇索引並不是主鍵索引這樣的單獨的索引類型,而是一種數據存儲方式。通俗的來說,單獨的索引是存儲了索引信息的B+Tree,而聚簇索引是在同一個結構中保存了B+Tree和數據行,即通過主鍵索引B+Tree的結構建立數據文件(網上的說法是索引和數據存儲在同一個文件中),因此聚簇索引是一種數據存儲方式。
如果對於索引的概念不是很熟悉,建議去查閱相關資料學習,索引是一個很龐大的知識結構。
Innodb表以主鍵索引建立后綴名為.MYD表存儲文件,普通索引亦以B+Tree實現,並保存在后綴名為.MYI的文件中,具體結構圖如下所示:
在查詢時選用主鍵索引和普通索引有什么不一樣呢?
以下面語句為例:
· select * from table where id = 5;(id為主鍵索引) 檢索過程如上圖綠色箭頭所示,直接在主索引樹上根據主鍵檢索
· select * from table where name = "Gates"; (name為普通索引)檢索過程如上圖紅色箭頭所示,先根據普通索引檢索普通索引樹得到id為5,然后再拿得到的id到主索引樹檢索得到結果。在這個過程中,回到主鍵索引樹搜索的過程,我們稱為回表。
從這里可以看出,基於非主鍵索引的查詢需要多掃描一棵索引樹。因此,我們在應用中應該盡量使用主鍵查詢。
解釋完普通索引和主鍵索引的檢索過程,讓我們來看看什么是覆蓋索引。
二、覆蓋索引和回表。
什么是“覆蓋索引”?
查詢的列被所建的輔助索引所覆蓋,無需回表。用大白話解釋就是,要查的數據直接可以從索引樹上就能取得,無需回表查找。
注意:不是所有類型的索引都可以成為覆蓋索引。覆蓋索引必須要存儲索引的列,而哈希索引、空間索引和全文索引等都不存儲索引列的值,所以MySQL只能使用B-Tree索引做覆蓋索引
結合上圖的例子:
· select id from table where name = "Gates"; 即為一個覆蓋索引。
三、分頁查詢使用場景
需求:查詢最近 7 天的訂單,並做分頁。訂單表數據量:3000W。
未經優化的SQL:
select * from t_trade_order
where create_time between '2019-10-17' and '2019-10-25'
limit 1000000, 10;
根據explain輸出的結果可知,這是一條慢查詢,在業務環境中不允許出現這樣的慢查詢。
我們都知道在做分頁時會用到Limit關鍵字去篩選所需數據,limit接受1個或者2個參數,接受兩個參數時第一個參數表示偏移量,即從哪一行開始取數據,第二個參數表示要取的行數。 如果只有一個參數,相當於偏移量為0。當偏移量很大時,如limit 100000,10 取第100001-100010條記錄,mysql會取出100010條記錄然后將前100000條記錄丟棄,這無疑是一種巨大的性能浪費。
終於轉入正題了,那我們應該怎樣優化呢?
四、延遲關聯優化
《高性能MySQL》書中其實也討論這個情況:
延遲關聯優化:通過使用覆蓋索引查詢返回需要的主鍵,再根據主鍵關聯原表獲得需要的數據。
select * from t_trade_order t
inner join (
select id from t_trade_order
where create_time between '2019-10-17' and '2019-10-25'
limit 1000000, 10
) e
on t.id = e.id;
根據explain分析,查詢時間僅為0.31,比普通的分頁查詢快了一個數量級。
想必大家會想知道,為什么延遲關聯對比普通分頁查詢可以起到這樣的優化效果呢?
這就涉及到上面所講的覆蓋索引和回表這兩個重要概念了!
我們來看看這兩條語句的執行流程就很清楚了。
優化前:
select * from t_trade_order
where create_time between '2019-10-17' and '2019-10-25'
limit 1000000, 10;
(create_time建表時被設置為普通索引)
1.在create_time索引樹上找到create_time=‘2019-10-17’的記錄,取得其id。
2.再到主索引樹查到對於id的記錄
3.如數量小於10,更新時間,循環步驟1、2
4. 。。。
5.在create_time索引樹取下一個值create_time='2019-10-25',不滿足條件,循環結束。
6.查詢結果放棄前1000000行,返回10行
顯然,普通的分頁查詢是逐一通過普通索引獲得id然后回表查詢,每次回表進行一次IO,造成相當大的性能浪費。
優化后
select * from t_trade_order t
inner join (
select id from t_trade_order
where create_time between '2019-10-17' and '2019-10-25'
limit 1000000, 10
) e
on t.id = e.id;
1.使用覆蓋索引,select id from t_trade_order where create_time between '2019-10-17' and '2019-10-25' limit 1000000, 10,查詢結果放棄前1000000行,返回10行,查詢出符合查詢范圍的id
2、回表關聯,根據獲得的id關聯主索引表,批量匹配得到結果。(只需回到主索引表一次)
由此可知,通過使用覆蓋索引查詢返回需要的主鍵,再根據這些主鍵關聯原表獲得需要的行,這可以減少MySQL回表的次數,也避免了MySQL直接在原表上掃描那些需要丟棄的行數(實則在普通索引樹上掃描,速度快很多)。
文章寫到這里,希望對大家理解延遲關聯優化有些許幫助。