多個單列索引和聯合索引的區別詳解(轉)

背景:
爲了提高數據庫效率,建索引是家常便飯;那麼當查詢條件爲2個及以上時,我們是創建多個單列索引還是創建一個聯合索引好呢?他們之間的區別是什麼?哪個效率高呢?我在這裏詳細測試分析下。


一、聯合索引測試

注:Mysql版本爲 5.7.20

創建測試表(表記錄數爲63188):

CREATE TABLE `t_mobilesms_11` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `userId` varchar(255) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '用戶id,創建任務時的userid',
  `mobile` varchar(24) NOT NULL DEFAULT '' COMMENT '手機號碼',
  `billMonth` varchar(32) DEFAULT NULL COMMENT '賬單月',
  `time` varchar(32) DEFAULT NULL COMMENT '收/發短信時間',
  `peerNumber` varchar(64) NOT NULL COMMENT '對方號碼',
  `location` varchar(64) DEFAULT NULL COMMENT '通信地(自己的)',
  `sendType` varchar(16) DEFAULT NULL COMMENT 'SEND-發送; RECEIVE-收取',
  `msgType` varchar(8) DEFAULT NULL COMMENT 'SMS-短信; MSS-彩信',
  `serviceName` varchar(256) DEFAULT NULL COMMENT '業務名稱. e.g. 點對點(網內)',
  `fee` int(11) DEFAULT NULL COMMENT '通信費(單位分)',
  `createTime` datetime DEFAULT NULL COMMENT '創建時間',
  `lastModifyTime` datetime DEFAULT NULL COMMENT '最後修改時間',
  PRIMARY KEY (`id`),
  KEY `聯合索引` (`userId`,`mobile`,`billMonth`)
) ENGINE=InnoDB AUTO_INCREMENT=71185 DEFAULT CHARSET=utf8 COMMENT='手機短信詳情'
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

我們爲userId, mobile, billMonth三個字段添加上聯合索引!

我們選擇 explain 查看執行計劃來觀察索引利用情況:


1.查詢條件爲 userid

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE userid='2222'
  • 1

這裏寫圖片描述

可以通過key看到,聯合索引有效


2.查詢條件爲 mobile

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE mobile='13281899972'
  • 1

這裏寫圖片描述
可以看到聯合索引無效


3.查詢條件爲 billMonth

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE billMonth='2018-04'
  • 1

這裏寫圖片描述
聯合索引無效


4.查詢條件爲 userid and mobile

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE userid='2222' AND mobile='13281899972'
  • 1

這裏寫圖片描述
聯合索引有效


5.查詢條件爲 mobile and userid

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE  mobile='13281899972' AND userid='2222' 
  • 1

這裏寫圖片描述
在4的基礎上調換了查詢條件的順序,發現聯合索引依舊有效


6.查詢條件爲 userid or mobile

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE userid='2222' OR mobile='13281899972'
  • 1

這裏寫圖片描述
and 換成 or,發現聯合所索引無效


7.查詢條件爲 userid and billMonth

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE userid='2222' AND billMonth='2018-04'
  • 1

這裏寫圖片描述
這兩個條件分別位於聯合索引位置的第一和第三,測試聯合索引依舊有效


8.查詢條件爲 mobile and billMonth

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE mobile='13281899972' AND billMonth='2018-04'
  • 1

這裏寫圖片描述
這兩個條件分別位於聯合索引位置的第二和第三,發現聯合索引無效


9.查詢條件爲 userid and mobile and billMonth

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE  userid='2222' AND mobile='13281899972' AND billMonth='2018-04'
  • 1

這裏寫圖片描述
所有條件一起查詢,聯合索引有效!(當然,這纔是最正統的用法啊!)


二、單列索引測試

創建三個單列索引:
這裏寫圖片描述

1.查詢條件爲 userid and mobile and billMonth

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE  userid='2222' AND mobile='13281899972' AND billMonth='2018-04'
  • 1

這裏寫圖片描述
我們發現三個單列索引只有 userid 有效(位置爲查詢條件第一個),其他兩個都沒有用上。

那麼爲什麼沒有用上呢?按照我們的理解,三個字段都加索引了,無論怎麼排列組合查詢,應該都能利用到這三個索引纔對!

其實這裏其實涉及到了mysql優化器的優化策略!當多條件聯合查詢時,優化器會評估用哪個條件的索引效率最高!它會選擇最佳的索引去使用,也就是說,此處userid 、mobile 、billMonth這三個索引列都能用,只不過優化器判斷只需要使用userid這一個索引就能完成本次查詢,故最終explain展示的key爲userid。

當然,如果優化器判斷本次查詢非要全使用三個索引才能效率最高,那麼explain的key就會是userid 、mobile 、billMonth,都會生效!


2.查詢條件爲 mobile and billMonth

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE mobile='13281899972' AND billMonth='2018-04'
  • 1

這裏寫圖片描述
我們發現此處兩個查詢條件只有 mobile 生效(位置也爲查詢條件第一個)


3.查詢條件爲 userid or mobile

EXPLAIN SELECT * FROM `t_mobilesms_11` WHERE  userid='2222' OR mobile='13281899972' 
  • 1

這裏寫圖片描述
這次把 and 換成 or,發現兩個查詢條件都用上索引了!

我們在網上可能常常看到有人說or會導致索引失效,其實這並不準確。而且我們首先需要判斷用的是哪個數據庫哪個版本,什麼引擎?

比如我用的是mysql5.7版本,innodb引擎,在這個環境下我們再去討論索引的具體問題。

關於or查詢的真相是:
所謂的索引失效指的是:假如or連接的倆個查詢條件字段中有一個沒有索引的話,引擎會放棄索引而產生全表掃描。我們從or的基本含義出發應該能理解並認可這種說法,沒啥問題。

此刻需要注意type類型爲index_merge
我查資料說mysql 5.0 版本之前 使用or只會用到一個索引(即使如上我給userid和mobile都建立的單列索引),但自從5.0版本開始引入了index_merge索引合併優化!也就是說,我們現在可以利用上多個索引去優化or查詢了。

index_merge作用:
1、索引合併是把幾個索引的範圍掃描合併成一個索引。
2、索引合併的時候,會對索引進行並集,交集或者先交集再並集操作,以便合併成一個索引。
3、這些需要合併的索引只能是一個表的。不能對多表進行索引合併。

index_merge應用場景:

1.對OR語句求並集,如查詢SELECT * FROM TB1 WHERE c1="xxx" OR c2=""xxx"時,如果c1和c2列上分別有索引,可以按照c1和c2條件進行查詢,再將查詢結果合併(union)操作,得到最終結果

2.對AND語句求交集,如查詢SELECT * FROM TB1 WHERE c1="xxx" AND c2=""xxx"時,如果c1和c2列上分別有索引,可以按照c1和c2條件進行查詢,再將查詢結果取交集(intersect)操作,得到最終結果

3.對AND和OR組合語句求結果


三、結論

通俗理解:
利用索引中的附加列,您可以縮小搜索的範圍,但使用一個具有兩列的索引 不同於使用兩個單獨的索引。複合索引的結構與電話簿類似,人名由姓和名構成,電話簿首先按姓氏對進行排序,然後按名字對有相同姓氏的人進行排序。如果您知道姓,電話簿將非常有用;如果您知道姓和名,電話簿則更爲有用,但如果您只知道名不姓,電話簿將沒有用處

所以說創建複合索引時,應該仔細考慮列的順序。對索引中的所有列執行搜索或僅對前幾列執行搜索時,複合索引非常有用僅對後面的任意列執行搜索時,複合索引則沒有用處。


重點:

多個單列索引多條件查詢時優化器會選擇最優索引策略可能只用一個索引,也可能將多個索引全用上! 但多個單列索引底層會建立多個B+索引樹,比較佔用空間,也會浪費一定搜索效率,故如果只有多條件聯合查詢時最好建聯合索引!


最左前綴原則:

顧名思義是最左優先,以最左邊的爲起點任何連續的索引都能匹配上,
注:如果第一個字段是範圍查詢需要單獨建一個索引
注:在創建聯合索引時,要根據業務需求,where子句中使用最頻繁的一列放在最左邊。這樣的話擴展性較好,比如 userid 經常需要作爲查詢條件,而 mobile 不常常用,則需要把 userid 放在聯合索引的第一位置,即最左邊


同時存在聯合索引和單列索引(字段有重複的),這個時候查詢mysql會怎麼用索引呢?

這個涉及到mysql本身的查詢優化器策略了,當一個表有多條索引可走時, Mysql 根據查詢語句的成本來選擇走哪條索引;


有人說where查詢是按照從左到右的順序,所以篩選力度大的條件儘量放前面。網上百度過,很多都是這種說法,但是據我研究,mysql執行優化器會對其進行優化當不考慮索引時,where條件順序對效率沒有影響真正有影響的是是否用到了索引


聯合索引本質:

當創建**(a,b,c)聯合索引時,相當於創建了(a)單列索引**,(a,b)聯合索引以及**(a,b,c)聯合索引**
想要索引生效的話,只能使用 a和a,b和a,b,c三種組合;當然,我們上面測試過,a,c組合也可以,但實際上只用到了a的索引,c並沒有用到!
注:這個可以結合上邊的 通俗理解 來思考!


其他知識點:

1、需要加索引的字段,要在where條件中
2、數據量少的字段不需要加索引;因爲建索引有一定開銷,如果數據量小則沒必要建索引(速度反而慢)
3、避免在where子句中使用or來連接條件,因爲如果倆個字段中有一個沒有索引的話,引擎會放棄索引而產生全表掃描
4、聯合索引比對每個列分別建索引更有優勢,因爲索引建立得越多就越佔磁盤空間,在更新數據的時候速度會更慢。另外建立多列索引時,順序也是需要注意的,應該將嚴格的索引放在前面,這樣篩選的力度會更大,效率更高


最後的說明:

網上關於索引優化等文章太多了,針對各個數據庫各個版本各種引擎都可能存在不一樣的說法

我們的SQL引擎自帶的優化也越來越強大,說不定你的某個SQL優化認知,其SQL引擎在某次升級中早就自優化了。

所以要麼跟進官方文檔,要麼關注數據庫大牛的最新文章,要麼在現有數據庫環境下自己去親手測試!

數據庫領域的水很深。。大家加油。。共勉 ~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章