你們好,我是飄渺!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(50) null, workcode varchar(50) null, 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(15, 2) NOT 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源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。