MySQL創建聯合索引,字段的先后順序,對查詢的影響分析


對於聯合索引我們知道,在使用的時候有一個最左前綴的原則,除了這些呢,比如字段放置的位置,會不會對索引的效率產生影響呢?

最左匹配原則

聯合索引時會遵循最左前綴匹配的原則,即最左優先,在檢索數據時從聯合索引的最左邊開始匹配,示例:

create table test
(
    id       bigint auto_increment primary key,
    column_1 bigint null,
    column_2 bigint null,
    column_3 bigint null
);

create index test_column_1_column_2_column_3_index
    on test(column_1, column_2, column_3);

比如上面的test表,我們建立了聯合索引index test_column_1_column_2_column_3_index on test (column_1, column_2, column_3);當我們進行查詢的時候,按照最左前綴的原則,當查詢(column_1)、(column_1,column_2)(column_1,column_2,column_3)這三種組合是可以用到我們定義的聯合索引的。如果我們查詢(column_1,column_3)就只能用到column_1的索引了。我們不用太關心索引的先后順序,什么意思呢?比如使用(column_1,column_2)和(column_2,column_1)的效果是一樣的,數據庫的查詢優化器會自動幫助我們優化我們的sql,看哪個執行的效率最高,最后才生成最后執行的sql

為什么會有最左前綴呢?

使用b+樹作為索引的存儲數據結構時,當我們創建聯合索引的時候,比如(column_1, column_2, column_3),b+樹建立索引是從左到右來建立搜索樹的,比如當我們來查詢的時候WHERE column_1 = 1 AND column_2 = 2 AND column_3 = 3。b+樹會先通過最左邊的(建立索引的字段的左邊的字段)字段,也就是column_1來確定下一步的查找對象,然后找到column_2,再通過column_2的索引找到column_3。所以(column_2,column_3)這樣的查詢命中不到索引了。因為最左前綴,一定是從最左邊的字段開始依次在b+樹的子節點查詢,然后確定下一個查找的子節點的數據。所以我們(column_1)、(column_1,column_2)、(column_1,column_2,column_3)這三種查詢條件是可以使用到索引的。

聯合索引的存儲結構

定義聯合索引(員工級別,員工姓名,員工出生年月),將聯合索引按照索引順序放入節點中,新插入節點時,先按照聯合索引中的員工級別比較,如果相同會按照員工姓名比較,如果員工級別和員工姓名都相同 最后是員工的出生年月比較。圖中從上到下,從左到右看,第一個B+樹的節點 是通過聯合索引的員工級別比較的,第二個節點是 員工級別相同,會按照員工姓名比較,第三個節點是 員工級別和員工姓名都相同,會按照員工出生年月比較。

聯合索引字段的先后順序

我們定義多個字段的聯合索引,會考慮到字段的先后順序。那么字段的先后順序真的會對查詢的效率產生影響嗎?比如上面的聯合索引index test_column_1_column_2_column_3_index on test (column_1, column_2, column_3);和index test_column_1_column_2_column_3_index on test (column_2, column_1, column_3);在查詢效率上有差別嗎?我們試驗下。

寫個函數批量插入下數據

CREATE PROCEDURE dowhile()
BEGIN
  DECLARE v1 INT DEFAULT 20000000;

  WHILE v1 > 0 DO
    INSERT INTO test.test (column_1, column_2, column_3) VALUES (RAND() * 20000000, RAND() * 10000, RAND() * 20000000);
    SET v1 = v1 - 1;
END WHILE;
END;

我們插入了20000000條數據,然后先設置索引(column_1, column_2, column_3)中column_1的數值范圍為0到20000000,column_2的范圍為0到10000。然后查詢,看看這個索引的效率。數據量太大,插入的時間可能要好久。為什么插入20000000條呢,因為b+樹可以高效存儲的數據條數就是21902400,具體見下文。

我們嘗試下查詢的效率:

SELECT * FROM test WHERE column_1=19999834 AND  column_2=3601
> OK
> 時間: 0.001s

EXPLAIN SELECT * FROM test WHERE column_1=19999834 AND  column_2=3601

我們看到索引的type為ref已經相當高效了。

type:這列最重要,顯示了連接使用了哪種類別,有無使用索引,是使用Explain命令分析性能瓶頸的關鍵項之一。

結果值從好到壞依次是:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

一般來說,得保證查詢至少達到range級別,最好能達到ref,否則就可能會出現性能問題。

然后我們看下插入的效率:

INSERT INTO test.test (column_1, column_2, column_3) VALUES (RAND() * 20000000, RAND() * 10000, RAND() * 20000000)
> Affected rows: 1
> 時間: 0.002s 

更改索引的順序:

drop index test_column_1_column_2_column_3_index on test;

create index test_column_2_column_1_column_3_index
	on test (column_2, column_1, column_3); 

我們把column_2和column_1的索引位置更換了一下,來比較聯合索引的先后順序對查詢效率的影響。

SELECT * FROM test WHERE  column_2=3601 AND column_1=19999834
> OK
> 時間: 0.001s

EXPLAIN SELECT * FROM test WHERE  column_2=3601 AND column_1=19999834 

發現更換了之后查詢時間上沒有什么出入,還和上個查詢的時間一樣,分析查詢的效率一樣很高。

再來看插入的效率:

INSERT INTO test.test (column_1, column_2, column_3) VALUES (RAND() * 20000000, RAND() * 10000, RAND() * 20000000)
> Affected rows: 1
> 時間: 0.003s

依然高效。

所以我們可以總結出來,聯合索引中字段的先后順序,在sql層面的執行效率,差別不大,是可以忽略的。分析上面索引的數據結構也是可以推斷出來的,無非就是當建立聯合索引,更換索引字段的先后順序,匹配每個字段鎖定的數據條數不一樣,但是對最終的查詢效率沒有太大的影響。但是這個字段的順序真的就不用考慮嗎?不是的,我們知道有最左匹配原則,所以我們要考慮我們的業務,比如說我們的業務場景中有一個字段enterpriseId,這個字段在80%的查詢場景中都會遇到,那么我們肯定首選將這個字段放在聯合索引字段的第一個位置,這樣就能保證查詢的高效,能夠命中我們建立的索引。

b+樹可以存儲的數據條數

如圖,為B+樹組織數據的方式:

實際存儲時當然不會每個節點只存3條數據。

以InnoDB引擎為例,簡單計算一下一顆B+樹可以存放多少行數據。

B+樹特點:只有葉子節點存儲數據,而非葉子節點存放的是用來找到葉子節點數據的索引(如上圖:key和指針)

InnoDB存儲引擎的最小存儲單元為16k(就像操作系統的最小單元為4k 即1頁),在這即B+樹的一個節點的大小為16k 

假設數據庫一條數據的大小為1k,則一個節點可以存儲16條數據

而非葉子節點,key一般為主鍵,假設8字節,指針在InnoDB中是6字節,一共為14字節,一個節點可以存儲 16384/14 = 1170個索引指針

可以算出一顆高度為2的樹(即根節點為存儲索引指針節點,還有1170個葉子節點存儲數據),每個節點可以存儲16條數據,一共1170*16條數據  = 18720條

高度為3的樹,可以存放 1170 * 1170 * 16 = 21902400條記錄

兩千多萬條數據,我們只需要B+樹為3層的數據結構就可以完成,通過主鍵查詢只需要3次IO操作就能查到對應記錄。

總結

對於聯合索引,我們不能忽略它的最左匹配原則,即在檢索數據時從聯合索引的最左邊開始匹配。對於創建聯合索引時,我們要根據我們的具體的查詢場景來定,聯合索引字段的先后順序,聯合索引字段的先后順序在sql層面上沒有太大差別,但是結合查詢的場景和最左匹配的原則,就能使一些查詢的場景不能很好的命中索引,這點使我們是不能忽略的。

 

參考:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM