用好組合索引,性能提高10倍不止!

你們好,我是飄渺!java

相信各位在面試時,一般會被問到「什麼是索引?」 而你確定能夠脫口而出:索引是提高查詢速度的一種數據結構。而索引之因此能提高查詢速度,在於它在插入時對數據進行了排序。程序員

在實際業務中,咱們會遇到不少複雜的場景,好比對多個列進行查詢。這時,可能會要求用戶建立多個列組成的索引,如列 a 和 b 建立的組合索引,但到底是建立(a,b)的索引,仍是(b,a)的索引,結果倒是徹底不一樣的。面試

今天,咱們就來聊聊更貼近業務實戰的組合索引,一塊兒來感覺一下組合索引的威力。(固然咯,文章中講的索引指的是B+樹索引,就是那個矮胖子啦)sql

組合索引

組合索引(Compound Index)是指由多個列所組合而成的 B+樹索引,這和B+ 樹索引的原理徹底同樣,只是單列索引是對一個列排序,如今是對多個列排序。微信

從上圖能夠看到,組合索引只是排序的鍵值從 1 個變成了多個,本質仍是一顆 B+ 樹索引。可是你必定要意識到(a,b)和(b,a)這樣的組合索引,其排序結果是徹底不同的。數據結構

假若有以下一張表test,給其建立了一個組合索引union_index架構

create table test
(
    id       int auto_increment primary key,
    name     varchar(50null,
    workcode varchar(50null,
    age      int         null
);

create index union_index on test (name, workcode);

那對於組合索引(name,workcode)來講,由於它對name,workcode作了排序,因此它能夠對下面兩個查詢進行優化併發

select * from test  where  name = 'zhang' ;
select * from test  where  name = 'zhang' and workcode='20190169';

值得注意的是,where後查詢列name 和workcode的順序無關,即便寫成where workcode = '20190169' and name ='zhang'仍然可使用組合索引(name,workcode)。高併發


可是下面的sql沒法使用組合索引(name,workcode),由於(name,workcode)排序並不能推出(workcode,name)排序。oop

select * from test where  workcode='20190169';

此外,一樣因爲索引(name,workcode)已排序,所以下面這條 SQL 依然可使用組合索引(name,workcode),以此提高查詢的效率:

select * from test  where  name = 'zhang' order by workcode;

一樣的緣由,索引(name,workcode)排序不能得出(workcode,name)排序,所以下面的 SQL 沒法使用組合索引(name,workcode):

select * from test  where  workcode = '20190169' order by name ;

講到這兒,你已經掌握了組合索引的基本內容,接下來咱們就看一看怎麼在業務實戰中正確地設計組合索引?

業務索引設計實戰

避免額外排序

在真實的業務場景中,你會遇到根據某個列進行查詢,而後按照時間排序的方式逆序展現。

好比在微博業務中,用戶的微博展現的就是根據用戶 ID 查詢出用戶訂閱的微博,而後根據時間逆序展現;又好比在電商業務中,用戶訂單詳情頁就是根據用戶 ID 查詢出用戶的訂單數據,而後根據購買時間進行逆序展現。

接着咱們看一下咱們線上一個真實的商機表,已經對字段作了簡化,只保留幾個關鍵字段,同時爲了方便測試,直接初始化了70多萬的數據。

CREATE TABLE t_opp_base
(
    id                  int            primary key auto_increment,
    opp_code            varchar(50)    NOT NULL,  -- 商機編碼
    opp_name            varchar(200)   NOT NULL,
    principal_user       varchar(50)    NOT NULL,  -- 責任人
    opp_status          char(1)        NOT NULL,
    opp_amount          decimal(152NOT NULL,
    opp_date            date           NOT NULL,
    opp_priority        char(15)       NOT NULL,
    remark              varchar(79)    NOT NULL,
    KEY `idx_opp_code` (opp_code),
    KEY `idx_principal_user` (principal_user)
);

其中:

  • 字段 id 是 INT 類型的主鍵;

  • 字段 opp_code,principal_user 因爲查詢的場景比較多,因此添加了單字段索引

  • 字段 opp_date、opp_status、opp_amount、opp_priority 用於商機的基本詳情,分別表示商機時間、當前商機的狀態、商機的總價值、商機的優先級。

在有了上述商機表後,當用戶查看javadaily負責的商機信息,而且須要根據商機時間排序查詢時,可經過下面的 SQL:

select * from t_opp_base  where principal_user = 'javadaily' order by opp_date DESC

但因爲上述表結構的索引設計時,索引 idx_principal_user 僅對列 principal_user 排序,所以在取出用戶的數據後,還須要一次額外的排序才能獲得結果,可查看執行計劃 EXPLAIN 確認:

 

經過上面的執行計劃能夠看出,SQL 語句的確可使用索引 idx_principal_user,但在 Extra 列中顯示的 Using filesort,表示還須要一次額外的排序才能獲得最終的結果。

因爲已對列 principal_user 建立索引,所以上述 SQL 語句並不會執行得特別慢,可是在高併發的狀況下,每次 SQL 執行都須要排序就會對業務的性能產生很是明顯的影響,好比 CPU 負載變高,QPS 下降。

要解決這個問題,最好的方法是:在取出結果時已經根據字段 opp_date 排序,這樣就不用額外的排序了。

因此,咱們在表t_opp_base上建立一個新的組合索引,idx_principal_oppdate,對字段(principal_user,opp_date)進行索引。

create index idx_principal_oppdate
    on t_opp_base (principal_user,opp_date);

這是再執行以前的sql,根據時間展現責任人負責的商機項目,其執行計劃爲:


這樣咱們就消除了Using filesort,提升了執行效率。

索引覆蓋,避免回表

基礎概念:

SQL須要二級索引查詢獲得主鍵值,而後再根據主鍵值搜索主鍵索引,最後定位到完整的數據。這一過程叫 回表。可是因爲二級組合索引的葉子節點,包含索引鍵值和主鍵值,若查詢的字段在二級索引的葉子節點中,則可直接返回結果,無需回表。這種經過組合索引避免回表的優化技術也稱爲 索引覆蓋(Covering Index)。

好比有下面一條SQL:

select principal_user,opp_date,opp_amount from t_opp_base  where principal_user = 'javadaily' ;

查看其執行計劃:

-> Index lookup on t_opp_base using idx_principal_oppdate (principal_user='javadaily')  (cost=312.51 rows=321) (actual time=0.452..0.908 rows=321 loops=1)

它的執行計劃顯示使用了以前建立的組合索引idx_principal_user,可是,因爲組合索引的葉子節點只包含(principal_user,opp_date,id),沒有字段 opp_amount 的值,因此須要經過 id 回表找到對應的 opp_amount

執行計劃中顯示執行成本cost爲312.51。(cost=312.51 表示的就是這條 SQL 當前的執行成本。不用關心 cost 的具體單位,你只需明白cost 越小,開銷越小,執行速度越快。)

若是想要避免回表,能夠經過索引覆蓋技術,建立(principal_user,opp_date,opp_amount)的組合索引,如:

alter table t_opp_base add index
 idx_principal_oppdate_amount(principal_user,opp_date,opp_amount);

再次查看執行計劃:

-> Index lookup on t_opp_base using idx_principal_oppdate_amount (principal_user='javadaily')  (cost=41.52 rows=321) (actual time=0.149..0.337 rows=321 loops=1)

執行成本有明顯的降低,從312.51降到了41.52,執行效率大大提升。

 

能夠看到執行計劃選擇了idx_principal_oppdate_amount索引,同時Extra列顯示爲 Using index,這就表示使用了覆蓋索引技術。

上面這條SQL一共返回321條記錄,這意味着在未使用索引覆蓋技術前,這條 SQL 須要總共回表 321 次, 每次從二級索引讀取到數據,就須要經過主鍵去獲取字段 opp_amount。在使用索引覆蓋技術後,無需回表,減小了 321次的回表開銷,這就是爲何執行成本會減小這麼多的緣由。

接下來咱們再看看這條SQL

select principal_user,sum(opp_amount) from t_opp_base  group by principal_user;

這條SQL根據商機責任人分組彙總,找出每一個責任人負責的商機價值總額,對責任人進行考覈。

爲了讓你們直觀感覺一下索引覆蓋的威力,我先刪掉以前建立的索引idx_principal_oppdate_amount

ALTER TABLE t_opp_base
drop INDEX idx_principal_oppdate_amount;

查看其執行計劃

 

 

能夠看到,這條 SQL 優化選擇了索引 idx_principal_oppdate,但因爲該索引沒有包含字段opp_amount,所以須要回表,根據 rows 預估出大約要回表 717912 次。同時也能夠看到執行成本爲76850.31,執行時間爲10.9秒。

而後咱們再次加上組合索引idx_principal_oppdate_amount

alter table t_opp_base add index
 idx_principal_oppdate_amount(principal_user,opp_date,opp_amount);

再次查看執行計劃

 

能夠看到,此次的執行計劃提高使用了組合索引 idx_principal_oppdate_amount,而且經過Using index 的提示,表示使用了索引覆蓋技術。同時執行時間爲1.74s,SQL性能大大提高。

 

這就是索引覆蓋技術的威力,並且這還只是基於 t_opp_base 表總共 70 萬條記錄。若表 t_opp_base 的記錄數越多,須要回表的次數也就越多,經過索引覆蓋技術性能的提高也就越明顯。

小結

組合索引也是一顆 B+ 樹,只是索引的列由多個組成,組合索引既能夠是主鍵索引,也能夠是二級索引。組合索引主要有如下三個優點:

  • 覆蓋多個查詢條件,如(a,b)索引能夠覆蓋查詢 a = ? 或者 a = ? and b = ?;

  • 避免 SQL 的額外排序,提高 SQL 性能,如 WHERE a = ? ORDER BY b 這樣的查詢條件;

  • 利用組合索引包含多個列的特性,能夠實現索引覆蓋技術,提高 SQL 的查詢性能,用好索引覆蓋技術,性能提高 10 倍不是難事。

好了,今天的文章就到這裏了,但願經過這篇文章你能夠在實際項目中合理的建立組合索引,提高查詢效率。最後,我是飄渺Jam,一名寫代碼的架構師,作架構的程序員,期待你的關注。我們有緣再見!

關注即送10個G的教學視頻,你還在等什麼,還不趕忙上車?

 

本文分享自微信公衆號 - JAVA日知錄(javadaily)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索