select執行流程
簡單介紹索引
-
官方介紹索引是幫助MySQL高效獲取數據的數據結構。更通俗的說,數據庫索引好比是一本書前面的目錄,能加快數據庫的查詢速度
-
一般來說索引本身也很大,不可能全部存儲在內存中,因此索引往往是存儲在磁盤上的文件中的(可能存儲在單獨的索引文件中,也可能和數據一起存儲在數據文件中)
-
我們通常所說的索引,包括聚集索引、覆蓋索引、組合索引、前綴索引、唯一索引等,沒有特別說明,默認都是使用B+樹結構組織(多路搜索樹,並不一定是二叉的)的索引
索引的優勢和劣勢
-
可以提高數據檢索的效率,降低數據庫的IO成本,類似於書的目錄
-
通過索引列對數據進行排序,降低數據排序的成本,降低了CPU的消耗
-
被索引的列會自動進行排序,包括【單列索引】和【組合索引】,只是組合索引的排序要復雜一些
-
如果按照索引列的順序進行排序,對應order by語句來說,效率就會提高很多
-
-
索引會占據磁盤空間
-
索引雖然會提高查詢效率,但是會降低更新表的效率
-
MySQL會級聯的去維護索引數據
-
索引的使用
索引的類型&創建
主鍵索引
-
索引列中的值必須是唯一的,切不能為空
-
alter table table_name add primary key (column_name);
普通索引
-
MySQL中基本索引類型,沒有什么限制,允許在定義索引的列中插入重復值和空值
-
alter table table_name add index index_name (column_name)
唯一索引
-
索引列中的值必須是唯一的,但是允許為空值
-
create unique index index_name on table(column_name) ;
全文索引
-
只能在文本類型CHAR,VARCHAR,TEXT類型字段上創建全文索引
-
字段長度比較大時,如果創建普通索引,在進行like模糊查詢時效率比較低,這時可以創建全文索引
-
全文索引一般很少使用,數據量比較少或者並發度低的時候可以用
-
數據量大或者並發度高的時候一般是用專業的工具lucene,es,solr
-
alter table table_name add fulltext index
idx_content
(column_name); -
可以使用MATCH() ... AGAINST語法執行全文搜索
-
select* from test where match (column_name) against ('巴拉巴拉');
-
空間索引
-
MySQL在5.7之后的版本支持了空間索引,
-
支持OpenGIS幾何數據模型。MySQL在空間索引這方面遵循OpenGIS幾何數據模型規則
-
不怎么用,想學習可以d參考官網
-
前綴索引
-
在文本類型如CHAR,VARCHAR,TEXT類列上創建索引時,可以指定索引列的長度,但是數值類型不能指定
-
alter table table_name add index index_name (column1(length));
組合索引
-
使用2個以上的字段創建的索引
-
組合索引的使用,需要遵循最左前綴原則(最左匹配原則,后面詳細說明)
-
一般情況下,建議使用組合索引代替單列索引(主鍵索引除外,具體原因后面詳細說明)
-
alter table_name add index index_name (column1,column2);
索引查看& 刪除
-
drop index index_name on table
-
show index from table_name \G
索引的數據結構
索引的要求
-
索引的數據結構,至少需要支持兩種最常用的查詢需求
-
等值查詢:根據某個值查找數據,比如
-
select * from user where name='張三';
-
-
范圍查詢:根據某個范圍區間查找數據,比如
-
select * from user where age>=18 and age<=25;
-
-
-
同時需要考慮時間和空間因素。在執行時間方面,我們希望通過索引,查詢數據的時間盡可能小
-
在存儲空間方面,我們希望索引不要消耗太多的內存空間和磁盤空間
索引數據結構的選用
-
常用的數據結構:Hash表、二叉樹、平衡二叉查找樹(紅黑樹是一個近似平衡二叉樹)、B樹、B+樹
-
下面我們挨着挨着來說明這些數據結構
Hash表
-
Java中的HashMap,TreeMap就是Hash表結構,以鍵值對的方式存儲數據
-
我們使用Hash表存儲表數據,Key可以存儲索引列,Value可以存儲行記錄或者行磁盤地址
-
Hash表在等值查詢時效率很高,時間復雜度為O(1)
-
不支持范圍快速查找,范圍查找時還是只能通過掃描全表方式
二叉查找樹
-
二叉樹特點:每個節點最多有2個分叉,左子樹和右子樹數據順序左小右大
-
二叉樹的檢索復雜度和樹高相關
-
將age列構建二叉樹,檢索age=76的數據,只需要三次IO就可以查詢到結果
-
但是也不是二叉樹就絕對會帶來查詢效率的提升
-
二叉樹在極端情況下會退化為一個單向列表,效率急速下降
-
平衡二叉查找樹
-
上面我們看到了二叉查找樹 極端情況的出現導致效率拉低
-
根本原因在於,該樹不平衡,太極端了
-
-
於是便有了這個平衡二叉查找樹
-
平衡二叉樹是采用二分法思維,平衡二叉查找樹除了具備二叉樹的特點
-
最主要的特征是樹的左右兩個子樹的層級最多相差1
-
在插入刪除數據時通過左旋/右旋操作保持二叉樹的平衡,不會出現左子樹很高、右子樹很矮的情況
-
使用平衡二叉查找樹查詢的性能接近於二分查找法,時間復雜度是 O(log2n)。查詢id=6,只需要兩次IO
-
但是也有問題暴露出來
-
查詢數據的時間復雜度和樹的搞得相關
-
平衡二叉樹不支持范圍查詢快速查找,范圍查詢時需要從根節點多次遍歷,查詢效率不高
-
B樹(改造二叉樹)
MySQL的數據是儲存在磁盤文件當中的
查詢處理數據時,需要先從磁盤中將數據加載到內存當中
訪問二叉樹的每個節點就會發生一次IO,如果想要減少磁盤IO操作,就需要盡量降低樹的高度
-
假如key為bigint=8字節,每個節點有兩個指針,每個指針為4個字節,一個節點占用的空間16個字節(8+4*2=16)
-
MySQL的InnoDB存儲引擎一次IO會讀取的一頁16K的數據量,而二叉樹一次IO有效數據量只有16字節,空間利用率極低
-
為了最大化利用一次IO空間,一個朴素的想法是在每個節點存儲多個元素,在每個節點盡可能多的存儲數據。每個節點可以存儲1000個索引(16k/16=1000),這樣就將二叉樹改造成了多叉樹,通過增加樹的叉樹,將樹從高瘦變為矮胖。構建1百萬條數據,樹的高度只需要2層就可以(1000*1000=1百萬),也就是說只需要2次磁盤IO就可以查詢到數據。磁盤IO次數變少了,查詢數據的效率也就提高了
-
這種數據結構我們稱為B樹,B樹是一種多叉平衡查找樹,如下圖主要特點:
-
B樹的節點中存儲着多個元素,每個內節點有多個分叉
-
節點中的元素包含鍵值和數據,節點中的鍵值從大到小排列。也就是說,在所有的節點都儲存數據
-
父節點當中的元素不會出現在子節點中
-
所有的葉子結點都位於同一層,葉節點具有相同的深度,葉節點之間沒有指針連接
-
對於一個主鍵索引,主鍵值bigint=8字節,data為記錄的磁盤地址為4個字節,一個元素占用空間12字節。一個磁盤塊大小為16k。磁盤塊中的分叉數=元素樹+1,假設可以存儲x個元素,12x+(x+1)4=16k,約等於1000,也就是說一頁中可以最多可以存儲1000個元素。 (1) :二層B樹結構可以存儲的數量10001000=1百萬,三層樹結構可以存儲的數量100010001000=1百億。 (2) :B樹的高度一般都是在2-4這個高度,樹的高度直接決定IO讀寫的次數以及查詢時間復雜度O(log2n))
我們來模擬一下查找查找主鍵為29的數據
-
第一次磁盤IO:將磁盤塊1加載到內存當中,然后遍歷比較 17 < 29 <35,鎖定磁盤塊1中 P2 這個指針
-
第二次磁盤IO:將磁盤塊3加載到內存中,然后遍歷比較 26 < 29 < 30,鎖定磁盤塊3中 P2 這個指針
-
第三次磁盤IO:將磁盤塊8加載到內存中,然后遍歷比較 28 < 29 = 29,找到了29所在位置,取出data
-
如果date中儲存的是表行數據,則直接返回
-
如果data中儲存的是磁盤地址,則根據該地址去磁盤中獲取數據,查詢終止
-
相比二叉平衡查找樹,在整個查找過程中,雖然數據的比較次數並沒有明顯減少,但是磁盤IO次數會大大減少。同時,由於我們的比較是在內存中進行的,比較的耗時可以忽略不計。B樹的高度一般2至3層就能滿足大部分的應用場景,所以使用B樹構建索引可以很好的提升查詢的效率
但是B樹也是有一定缺陷的:
-
B樹不支持范圍查詢的快速查找,如果我們想要查找15和26之間的數據,查找到15之后,需要回到根節點重新遍歷查找,需要從根節點進行多次遍歷,查詢效率有待提高
-
如果data存儲的是行記錄,行的大小隨着列數的增多,所占空間會變大。這時,一個頁中可存儲的數據量就會變少,樹相應就會變高,磁盤IO次數就會變大
B+樹(改造B樹)
在B樹基礎上,MySQL在B樹的基礎上繼續改造,使用B+樹構建索引。B+樹和B樹最主要的區別在於非葉子節點是否存儲數據的問題
B樹:非葉子節點和葉子節點都會存儲數據
B+樹:只有葉子節點才會存儲數據,非葉子節點只存儲鍵值。葉子節點之間使用雙向指針連接,最底層的葉 子節點形成了一個雙向有序鏈表
-
B+樹的最底層葉子節點包含所有索引項
-
B+樹查找數據,由於數據都存放在葉子節點,所以每次查找都需要檢索到葉子節點,才能查詢到數據
-
B樹查找數據時,如果在內節點中查找到數據,可以立即返回,比如查找值等於17的數據,在根節點中直接就可以找到,不需要再向下查找,具備中路返回的特點
我們來模擬一下查找查找主鍵為29的數據
-
第一次磁盤IO:將磁盤塊1加載到內存當中,然后遍歷比較 28 < 29 ,鎖定磁盤塊1中 P2 這個指針
-
第二次磁盤IO:將磁盤塊3加載到內存中,然后遍歷比較 29 < 36,鎖定磁盤塊3中 P1 這個指針
-
第三次磁盤IO:將磁盤塊7加載到內存中,然后遍歷比較 28 < 29 = 29,找到了29所在位置,取出data
-
如果date中儲存的是表行數據,則直接返回
-
如果data中儲存的是磁盤地址,則根據該地址去磁盤中獲取數據,查詢終止
-
我們再來模擬一下范圍查詢 15 -- 29
-
第一次磁盤IO:將磁盤塊1加載到內存當中,然后遍歷比較 15 < 28 ,鎖定磁盤塊1中 P1 這個指針
-
第二次磁盤IO:將磁盤塊2加載到內存中,然后遍歷比較10 < 15 < 17,鎖定磁盤塊2中 P2 這個指針
-
第三次磁盤IO:將磁盤塊5加載到內存中,然后遍歷比較 15 = 15,找到了25所在位置
-
葉子節點之間使用雙向指針連接,最底層的葉子節點形成了一個雙向有序鏈表
-
-
第四次磁盤IO:將磁盤塊6加載到內存中,然后遍歷比較 15 < 17 < 26 < 29
-
葉子節點之間使用雙向指針連接,最底層的葉子節點形成了一個雙向有序鏈表
-
-
第五次磁盤IO:將磁盤塊7加載到內存中,然后遍歷比較 15 < 28 < 29 = 29,將所有結果緩存到結果集
可以看到B+樹可以保證等值和范圍查詢的快速查找,MySQL的索引就采用了B+樹的數據結構
MyISAM使用B+樹構建索引
MyISAM的數據文件和索引文件是分開存儲的。MyISAM使用B+樹構建索引樹時,葉子節點中存儲的鍵值為索引列的值,數據為索引所在行的磁盤地址
我們先創建一個表:id為主鍵,age為普通索引
CREATE TABLE `user_myisam` ( `id` INT ( 11 ) NOT NULL AUTO_INCREMENT, `username` VARCHAR ( 20 ) DEFAULT NULL, `age` INT ( 11 ) DEFAULT NULL, PRIMARY KEY ( `id` ) USING BTREE, KEY `idx_age` ( `age` ) USING BTREE ) ENGINE = MyISAM AUTO_INCREMENT = 1 DEFAULT CHARSET = utf8;
導入一部分初始數據,用作演示
主鍵索引的B+樹
-
表user_myisam的索引存儲在索引文件:/var/lib/mysql/數據庫名/user_myisam.MYI中,
-
數據文件存儲在數據文件/var/lib/mysql/數據庫名/user_myisam.MYD中
等值查詢數據:select * from user_myisam where id=30;
-
第一次磁盤IO:先在主鍵樹中從根節點開始檢索,將根節點加載到內存,比較30<56,走左路
-
第二次磁盤IO:將左子樹節點加載到內存中,比較20<30<49,向下檢索
-
第三次磁盤IO:檢索到葉節點,將節點加載到內存中遍歷,比較20<30,30=30。查找到值等於30的索引項
-
第四次磁盤IO:從索引項中獲取磁盤地址,然后到數據文件user_myisam.MYD中獲取對應整行記錄
-
將記錄返給客戶端
-
磁盤IO次數: 3 + 1
范圍查詢數據 :select * from user_myisam where id between 30 and 49;
-
第一次磁盤IO:先在主鍵樹中從根節點開始檢索,將根節點加載到內存,比較30<56,走左路
-
第二次磁盤IO:將左子樹節點加載到內存中,比較20<30<49,向下檢索
-
第三次磁盤IO:檢索到葉節點,將節點加載到內存中遍歷,比較20<30,30=30。查找到值等於30的索引項
-
第四次磁盤IO:從索引項中獲取磁盤地址,然后到數據文件user_myisam.MYD中獲取對應整行記錄 緩存到結果集中
-
葉子節點之間使用雙向指針連接,最底層的葉子節點形成了一個雙向有序鏈表
-
-
第五次磁盤IO: 查詢語句時范圍查找,需要向后遍歷底層葉子鏈表,找到49的索引項
-
第六次磁盤IO:從索引項中獲取磁盤地址,然后到數據文件user_myisam.MYD中獲取對應整行記錄 緩存到結果集中
-
將記錄返給客戶端
-
磁盤IO次數: 2 + 2 + 2
輔助索引的B+樹
在 MyISAM 中,輔助索引和主鍵索引的結構是一樣的,沒有任何區別,葉子節點的數據存儲的都是行記錄的磁盤地址。只是主鍵索引的鍵值是唯一的,而輔助索引的鍵值可以重復
查詢數據時,由於輔助索引的鍵值不唯一,可能存在多個擁有相同的記錄,所以即使是等值查詢,也需要按照范圍查詢的方式在輔助索引樹中檢索數據
InnoDB使用B+樹構建索引
InnoDB索引簡介
每個InnoDB表都有一個聚簇索引 ,聚簇索引使用B+樹構建,葉子節點存儲的數據是整行記錄。一般情況下,聚簇索引等同於主鍵索引,當一個表沒有創建主鍵索引時,InnoDB會自動創建一個ROWID字段來構建聚簇索引。InnoDB創建索引的具體規則如下:
在表上定義主鍵PRIMARY KEY,InnoDB將主鍵索引用作聚簇索引
如果表沒有定義主鍵,InnoDB會選擇第一個不為NULL的唯一索引列用作聚簇索引
如果以上兩個都沒有,InnoDB 會使用一個6 字節長整型的隱式字段 ROWID字段構建聚簇索引。該ROWID字段會在插入新行時自動遞增
除聚簇索引之外的所有索引都稱為輔助索引。在中InnoDB,輔助索引中的葉子節點存儲的數據是該行的主鍵值都。 在檢索時,InnoDB使用此主鍵值在聚簇索引中搜索行記錄
我們還是先創建一個表用來做測試,id為主鍵,age為普通索引
CREATE TABLE `user_innodb` ( `id` INT ( 11 ) NOT NULL AUTO_INCREMENT, `username` VARCHAR ( 20 ) DEFAULT NULL, `age` INT ( 11 ) DEFAULT NULL, PRIMARY KEY ( `id` ) USING BTREE, KEY `idx_age` ( `age` ) USING BTREE ) ENGINE = INNODB;
導入一部分初始數據,用作演示
-
InnoDB的數據和索引存儲在一個文件:/var/lib/mysql/數據庫名/user_innodb.ibd中
-
InnoDB的數據組織方式,是聚簇索引
主鍵索引的B+樹
-
主鍵索引的葉子節點會存儲數據行,輔助索引只會存儲主鍵值
-
InnoDB要求表必須有一個主鍵索引(MyISAM 可以沒有)
等值查詢數據:select * from user_innodb where id=30;
-
第一次磁盤IO:先在主鍵樹中從根節點開始檢索,將根節點加載到內存,比較30<56,走左路
-
第二次磁盤IO:將左子樹節點加載到內存中,比較20<30<49,向下檢索。
-
第三次磁盤IO:檢索到葉節點,將節點加載到內存中遍歷,比較 20 < 30 = 30 直接可以獲取整行數據
-
磁盤IO次數:3次
范圍查詢數據 :select * from user_innodb where id between 30 and 50;
-
第一次磁盤IO:先在主鍵樹中從根節點開始檢索,將根節點加載到內存,比較30<56,走左路
-
第二次磁盤IO:將左子樹節點加載到內存中,比較20<30<49,向下檢索。
-
第三次磁盤IO:檢索到葉節點,將節點加載到內存中遍歷,比較 20 < 30 = 30 ,將數據緩存到結果集中
-
第四次磁盤IO:向后遍歷底層葉子鏈表 ,49 < 50 = 50,將這兩個數據緩存到結果集中
-
磁盤IO次數:4次
可以看到,因為在主鍵索引中直接存儲了行數據,所以InnoDB在使用主鍵查詢時可以快速獲取行數據。
-
當表很大時,與在索引樹中存儲磁盤地址的方式相比
-
因為不用再去磁盤中獲取數據,所以聚簇索引通常可以節省磁盤IO操作
輔助索引的B+樹
除聚簇索引之外的所有索引都稱為輔助索引,InnoDB的輔助索引只會存儲主鍵值而非磁盤地址
-
底層葉子節點的按照(age,id)的順序排序,先按照age列從小到大排序,age列相同時按照id列從小到大排序
-
使用輔助索引需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后使用主鍵到主鍵索引中檢索獲得數據
等值查詢數據:select * from user_innodb where age=7;
-
第一次磁盤IO:先在主鍵樹中從根節點開始檢索,將根節點加載到內存,比較7 < 8,走左路
-
第二次磁盤IO:將左子樹節點加載到內存中,比較6 < 7,走右路。
-
第三次磁盤IO:將節點加載到內存中遍歷,,比較 6 < 7 = 7,檢索到葉節點,獲取主鍵id = 49
-
第3 + 3次磁盤IO:去主鍵B+樹中根據ID查詢id為49的數據
-
其他情況磁盤IO:如果在這張表中,age為7的數據不止一條,會中繼的不斷將結果緩存到結果集中一起返回
-
磁盤IO次數:不可計算
根據在輔助索引樹中獲取的主鍵id,到主鍵索引樹檢索數據的過程稱為回表查詢
范圍查詢數據:select * from t_user_innodb where age between 5 and 8;
-
輔助索引的范圍查詢流程和等值查詢基本一致,先使用輔助索引到葉子節點檢索到第一個符合條件的索引項
-
然后向后遍歷,直到遇到第一個不符合條件的索引項,終止。
-
檢索過程中需要將符合篩選條件的id值,依次到主鍵索引檢索將檢索的數據放入結果集中。
-
最后將查詢結果返回客戶端
組合索引儲存結構
下面我們了解一下組合索引的B+樹是如何構建,查找的時候又是如何查找的
首先我們還是創建一個測試表:id為主鍵,idx_abc(a,b,c) 為組合索引
CREATE TABLE `multiple_index` ( `id` INT ( 11 ) NOT NULL AUTO_INCREMENT, `a` INT ( 11 ) DEFAULT NULL, `b` INT ( 11 ) DEFAULT NULL, `c` VARCHAR ( 10 ) DEFAULT NULL, `d` VARCHAR ( 10 ) DEFAULT NULL, PRIMARY KEY ( `id` ) USING BTREE, KEY `idx_abc` ( `a`, `b`, `c` ) ) ENGINE = INNODB;
我們還是老樣子,導入一點測試數據進去
上圖構建的B+樹索引結構如圖所示
-
索引樹中節點中的索引項按照(a,b,c)的順序從小到大排列
-
先按照a列排序,a列相同時按照b列排序,b列相同按照c列排序
-
在最底層的葉子節點中,如果兩個索引項的a,b,c三列都相同,索引項按照主鍵id排序
組合索引的查找方式
select * from multiple_index where a=55 and b=555 and c=5c;
-
第一次磁盤IO:先在索引樹中從根節點開始檢索,將根節點加載到內存,先比較a列 55 < 88
-
第二次磁盤IO:將左子樹節點加載到內存中,先比較a列,44 < 55,走右路
-
第三次磁盤IO:達到葉節點,將節點加載到內存中從前往后遍歷比較
-
55 = 55 符合要求
-
555 = 555 符合要求
-
5c = 5c 符合要求
-
取出data的值,即 id = 5,
-
-
第N次磁盤IO:回表去主鍵B+樹中檢索id=5的數據放入結果集中
-
其他情況磁盤IO:如果在這張表中,abc符合要求的數據不止一條,會中繼的不斷將結果主鍵id緩存到起來,再依次去主鍵B+樹種查詢數據,並依次將結果緩存到結果集中,最后統一返回
-
磁盤IO次數:不可計算
組合索引的最左前綴匹配原則
最左前綴匹配原則和聯合索引的索引存儲結構和檢索方式是有關系的
在上面的查找方式中我們可以發現,首先會根據:where a=55 and b=555 and c=5c;
-
a 位於最左側的第一個條件,也是拿去匹配索引的第一個屬性,在B+樹中優先比較最左側的屬性值
-
上面我們也已經說到了:先按照a列排序,a列相同時按照b列排序,b列相同按照c列排序
-
葉子節點按照第一列a列從左到右遞增排列
-
-
所以當我們使用 where a=55 and b=555 and c=5c去查詢數據的時候,B+樹會先比較a列來確定下一步應該搜索的方向,往左還是往右。如果a列相同再比較b列。但是如果查詢條件沒有a列,B+樹就不知道第一步應該從哪個節點查起
-
所以,就有一個規矩出現:
-
想要命中組合索引,提高響應性能,第一個條件就必須要有,比如a=55
-
如果你的a沒有賦值,where b = 555 and c=5c;
-
在組合B+樹中因為a沒有值,無法在組合索引B+樹種定位數據,也無法回表去主鍵B+樹種快速定位數據
-
-
最后只能走全表掃描
-
-
其實我們在創建組合索引的時候
-
InnoDB給我們一共創建三個索引: a 、a,b、a,b,c
-
或許有人會問了,為什么沒有:a,c
-
因為a,過了之后就改匹配b了,此時你b沒值,根本就輪不到c去匹配,所以無法命中組合索引
-
-
還有一點就是:
-
組合索引的最左前綴匹配原則:使用組合索引查詢時,mysql會一直從左向右匹配直至遇到范圍查詢(>、<、between、like)就停止匹配
-
另外書寫SQL條件的順序,不一定是執行時候的where條件順序,優化器會幫助我們優化成索引可以識別的形式
-
比如下面這個(唯一保證的是絕對正向優化,不會負向優化)
-
#無法命中組合索引 select * from multiple_index where b=555 and a=55 and c=5c; #優化器正向優化:優化器會按照索引的順序優化 select * from t_multiple_index where a=55 and b=555 and c=5c;
-
一顆索引樹等價與三顆索引樹,從另一方面了說,組合索引也為我們節省了磁盤空間。所以在業務中 盡量選用組合索引,能使用組合索引就不要使用單列索引
-
組合索引創建原則
-
頻繁出現在where條件中的列,建議創建組合索引。
-
頻繁出現在order by和group by語句中的列,建議按照順序去創建組合索引
-
order by a,b 需要組合索引列順序(a,b)。如果索引的順序是(b,a),是用不到索引的
-
-
常出現在select語句中的列,也建議創建組合索引
對於第1種情況和第3種情況,組合索引創建的順序對其來說是等價的,這種情況下組合索引中的順序,是很重要的。由於組合索引會使用到最左前綴原則,使用頻繁的列在創建索引時排在前面
select * from t where a=1 and b>2 order by c
-
大家覺得這個組合索引該如何創建
-
這種情況實際上比較的是b的區分度和c的區分度,如果b的區分度比較差,建議使用ac。如果c的區分度比較差,建議使用a,b
-
可以考慮建立 (a,c)聯合索引:這樣 a等值查詢c就是已經排好序的了
-
覆蓋索引
-
前面我們提到,根據在輔助索引樹查詢數據時,首先通過輔助索引找到主鍵值,然后需要再根據主鍵值到主鍵索引中找到主鍵對應的數據。這個過程稱為回表
使用輔助索引查詢比基於主鍵索引的查詢多檢索了一棵索引樹,那是不是所有使用輔助索引的查詢都需要回表查詢呢?
-
在上面的組合索引中,我們為(a,b,c)創建了組合索引,在B+索引樹種葉子節點一共包含了四個數據,
-
分別是【a,b,c,id】
-
-
然后我們查詢某條數據,根據組合索引定位主鍵id,然后回表主鍵B+索引樹查詢該條數據
-
其實也不是所有的查詢都需要回標查詢
-
select a from multiple_index where a=11 and b=111; select a,b from multiple_index where a=22 and b=222; select a,b,c from multiple_index where a=33 and b=333; select a,b,c,id from multiple_index where a=44 and b=444;
-
比如上面這些sql,我們想要查詢的數據,可以直接在輔助索引樹上全部獲取 ,此時則不需要在回表操作
-
這種現象就是覆蓋索引
-
使用explain工具查看執行計划,可以看到 extra 中“Using index”,代表使用了覆蓋索引
-
索引條件下推ICP
是MySQL5.6對使用索引從表中檢索行的一種優化。可以通過參數optimizer_switch控制ICP的開始和關閉
ICP的目的是為了減少回表次數,可用於 InnoDB 和 MyISAM 表,對於InnoDB表ICP僅用於輔助索引
show VARIABLES like 'optimizer_switch'; SET optimizer_switch = 'index_condition_pushdown=off';#關閉ICP SET optimizer_switch = 'index_condition_pushdown=on'; #開啟ICP我們還是以上面的組合索引為例,來講解ICP的作用
select * from t_multiple_index where a=11 and b>=111 and c='1c' and d='1d';
-
在使用組合索引在檢索數據時是使用最左前綴原則來定位記錄,那不滿足最左前綴的索引列,MySQL會怎么處理?
-
我們去看看執行計划是怎么說的 !
-
之前我們說過:
-
組合索引的最左前綴匹配原則:mysql會一直從左向右匹配直至遇到范圍查詢(>、<、between、like)就停止匹配
-
所以上面的sql只使用了 (a,b)這個索引來檢索數據
-
MySQL首先會在組合索引中定位到第一個滿足 a=11 and b>=111 的索引項
-
然后我們看上圖的執行計划 :
-
extra列中的“Using index condition” 執行器表示使用了索引條件下推ICP
-
-
MySQL5.6之前不使用ICP的操作
-
執行器使用索引(a,b,c),篩選條件a=11 and b>=111
-
根據最左前綴原則聯合索引檢索定位到索引項(11,111,1c,id=1)
-
在我們的測試表中只有一條數據,實際中會查詢出很多符合(a=11 and b>=111)的數據項
-
然后這些數據項全部回表查詢,獲得多條行記錄
-
-
然后使用id=1回表查詢,獲得id=1的行記錄。返回給MySQL服務層
-
MySQL服務層使用剩余條件c='1c' and d='1d'過濾,不符合要求,直接丟棄
-
返回結果集
-
在這個過程中,由於(a=11 and b>=111)可能會定位到多條數據
-
定位到的所有數據全部都會回表查詢,然后得到多條行數據
-
再然后MySQL服務層使用剩余條件c='1c' and d='1d'過濾剛剛得到的多行數據,不符合要求,直接丟棄
-
整個過程,無用回表操作多次,磁盤IO倍增,效率急劇下降
-
MySQL5.6之后使用ICP的操作
-
可以在輔助索引B+樹索引遍歷過程中,對where中包含剩余條件c='1c' and d='1d'先做判斷,只有滿足條件的才會回表查詢讀取行數據。這么做可以減少回表查詢,從而減少磁盤IO次數
-
執行器使用索引(a,b,c),篩選條件a=11 and b>=111
-
根據最左前綴原則聯合索引檢索定位到索引項(11,111,1c,id=1)
-
在我們的測試表中只有一條數據,實際中會查詢出很多符合(a=11 and b>=111)的數據項
-
【不使用ICP】然后這些數據項全部回表查詢,獲得多條行記錄
-
【使用ICP】使用ICP下推條件c='1c' 判斷 ,不滿足條件,直接丟棄 ,滿足條件再回表
-
因為還有d沒有匹配,此時可能還是有冗余數據存在,但是會少很多
-
-
回表查詢,得到所有符合a=11 and b>=111 and c='1c'的數據行
-
然后MySQL服務層使用剩余條件d='1d'過濾,不符合要求,直接丟棄
-
-
最后將結果集返回
看了上面你可能會有疑問了?
-
為什么不在輔助索引樹中就把d='1d'一起給過濾了,而是只單單過濾了c='1c'
-
給你一分鍾好好想想,我再給你一點提示
-
-
上面的圖中,有d的數據嗎?如果沒有,在哪里去拿來比???
所以,接下來,把本子拿出來記好,這是要考的
-
不使用ICP,不滿足最左前綴的索引條件的比較是在存儲引擎層進行的,非索引條件的比較是在Server層進行的
-
使用ICP,所有的索引條件的比較是在存儲引擎層進行的,非索引條件的比較是在Server層進行的
-
ICP可以有效減少回表查詢次數、減少了磁盤IO次數、服務層與存儲引擎的交互次數。
索引創建原則
哪些情況需要創建索引
-
頻繁出現在where 條件判斷,order排序,group by分組字段
-
select 頻繁查詢的列,考慮是否需要創建聯合索引(覆蓋索引,不回表)
-
多表join關聯查詢,on字段兩邊的字段都要創建索引
索引創建建議
-
表記錄很少不需創建索引 (索引是要有存儲的開銷) .
-
一個表的索引個數不能過多
-
空間開銷、數據維護同時需要維護索引樹,性能被拉低
-
-
頻繁更新的字段不建議作為索引
-
頻繁更新的字段引發頻繁的頁分裂和頁合並,性能消耗比較高
-
-
區分度低的字段,不要建索引
-
比如你的性別這種字段建來干啥,加上回表查詢消耗,還不如全表掃描呢
-
-
在InnoDB存儲引擎中,主鍵索引建議使用自增的長整型,避免使用很長的字段
-
主鍵索引樹一個頁節點是16K,主鍵字段越長,一個頁可存儲的數據量就會越少
-
輔助索引樹上葉子節點存儲的數據是主鍵值,主鍵值越長,一個頁可存儲的數據量就會越少
-
都會導致查詢時磁盤IO次數會增多,查詢效率會降低
-
-
不建議用無序的值作為索引。例如身份證、UUID (踩坑了吧)
-
更新數據時會發生頻繁的頁分裂,頁內數據不緊湊,浪費磁盤空間
-
-
盡量創建組合索引,而不是單列索引
-
1個組合索引等同於多個索引效果,節省空間
-
可以使用覆蓋索引
-
組合索引應該把把頻繁的列,區分度高的值放在前面
-
頻繁使用代表索引的利用率高,區分度高代表篩選粒度大,可以盡量縮小篩選范圍
-
-
索引失效分析
先創建演示表: id為主鍵,(name,age,address)為組合索引,school 沒有索引
CREATE TABLE `test` ( `id` INT ( 11 ) NOT NULL AUTO_INCREMENT, `name` VARCHAR ( 10 ) DEFAULT NULL, `age` INT ( 11 ) DEFAULT NULL, `address` VARCHAR ( 10 ) DEFAULT NULL, `school` VARCHAR ( 10 ) DEFAULT NULL, PRIMARY KEY ( `id` ) USING BTREE, KEY `idx_name_age_address` ( `name`, `age`, `address` ) ) ENGINE = INNODB;
-
再來一點測試數據
全值匹配我最愛
就是索引全值匹配 比如test表的輔助索引有【(name),(name,age),(name,age,address)】
最佳左前綴法則
帶頭索引不能死,中間索引不能斷
不要在索引上做計算
不要進行這些操作:計算、函數、自動/手動類型轉換,不然會導致索引失效而轉向全索引掃描或全表掃描
范圍條件右邊的列失效
不能繼續使用索引中范圍條件(bettween、<、>、in等)右邊的列
盡量使用覆蓋索引
索引字段上不要使用不等
索引字段上使用(!= 或者 < >)判斷時,會導致索引失效而轉向全表掃描
索引字段上不要判斷null
索引字段上使用 is not null 判斷時,會導致索引失效而轉向全表掃描
使用 is null 是可以使用索引的
索引字段使用like不以通配符開頭
索引字段使用like以通配符開頭(‘%字符串’)時,會導致索引失效而轉向全表掃描
解決like '%字符串%' 時,索引失效問題的方法 :使用覆蓋索引可以解決
like與范圍條件(bettween、<、>、in等)不同的是:不會導致右邊的索引失效
索引字段字符串要加單引號
索引字段是字符串,但查詢時不加單引號,會導致索引失效而轉向全表掃描
索引字段不要使用or
索引字段使用 or 時,會導致索引失效而轉向全表掃描