對于單列索引,沒有太多的話題,但是對于多列索引的建立,一個好的多列索引能使用的場景應可以涵蓋很多,減少其他不必要的索引空間,就有很多事情需要注意。
0.首先來了解索引的物理結構:
http://www.lxweimin.com/p/1775b4ff123a
1.where 子句中的多列索引
如果表有多個列索引,可以使用任何左邊的前綴索引的優化器來查找行。如何通過前綴索引來查找行我們通過如下例子來詳細了解:
現有表formatting,數據如下圖所示:
在該表上建立多列索引:
create index idx_custid_qty_empid on formatting(custid,qty,empid)
1.1可以完全使用該索引的優化的情況
例如,如果你有一個三列的索引(col1、col2 col3),有索引搜索功能(col1),(col1,col2),(col1、col2、col3)。
1)where中條件只有col1等于常量;
說明:表中custid為A的數據共4行,查詢中索引優化得到的等于結果數據。
mysql> explain select orderid,orderdate from formatting where custid='A'\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: ref possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 32 ref: const rows: 4 filtered: 100.00 Extra: Using index
2)where中條件只有col1為范圍(>,<,>=,<=);
說明:表中custid大于A的有5行,索引起到完全優化的作用。
`mysql> explain select orderid,orderdate from formatting where custid>'A'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: formatting
partitions: NULL
type: range
possible_keys: idx_custid_qty_empid
key: idx_custid_qty_empid
key_len: 32
ref: NULL
rows: 5
filtered: 100.00
Extra: Using where; Using index`
3)where中條件有col1,col2,且col1等于常量,col2等于常量;
說明:表中custid為A,qty等于20的數據只有一條,索引完全優化查詢。
mysql> explain select orderid,orderdate from formatting where custid='A' and qty=20\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: ref possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 36 ref: const,const rows: 1 filtered: 100.00 Extra: Using index
4)where中條件有col1,col2,且col1等于常量,col2等于范圍;
說明:表中custid為A,qty大于等于20的數據有2條,索引完全優化查詢。
mysql> explain select orderid,orderdate from formatting where custid='A' and qty>=20\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: range possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 36 ref: NULL rows: 2 filtered: 100.00 Extra: Using where; Using index
5)where中條件有col1,col2,col3,且col1,col2,col3均等于常量;
說明:表中custid為A,qty等于10,empid等于2的數據有1條,索引完全優化查詢。
mysql> explain select orderid,orderdate from formatting where custid='A' and qty=10 and empid=2\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: ref possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 40 ref: const,const,const rows: 1 filtered: 100.00 Extra: Using index
6)where中條件有col1,col2,col3,且col1,col2均等于常量,col3為范圍;
說明:表中custid為A,qty等于10,empid大于2的數據有1條,索引完全優化查詢。
mysql> explain select orderid,orderdate from formatting where custid='A' and qty=10 and empid>2\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: range possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 40 ref: NULL rows: 1 filtered: 100.00 Extra: Using where; Using index
1.2可以部分使用該索引的優化的情況
1)where中條件col1等于常量,無col2,col3的常量或范圍,只能用到col1的索引;
說明:custid='A' and empid=2的數據僅有一條,但由于empid在多列索引中為第三列,故僅能使用多列索引優化custid='A'的4條數據。
mysql> explain select orderid,orderdate from formatting where custid='A' and empid=2\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: ref possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 32 ref: const rows: 4 filtered: 11.11 Extra: Using where; Using index
2)where中條件col1等于常量,col2的范圍,col3的常量或范圍,只能用到col1、col2的索引;
說明:custid='A' and qty<20 and empid=2的數據僅有一條,但由于qty為范圍,故僅能使用多列索引優化custid='A' and qty<20的2條數據。
mysql> explain select orderid,orderdate from formatting where custid='A' and qty<20 and empid=2\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: range possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 36 ref: NULL rows: 2 filtered: 11.11 Extra: Using where; Using index
3)where中條件col1的范圍,col2、col3的常量或范圍,只能用到col1的索引;
說明:custid>'A' and empid=2的數據僅有一條,但由于custid為范圍查詢,故僅能使用多列索引優化custid>'A'的5條數據。
mysql> explain select orderid,orderdate from formatting where custid>'A' and empid=2\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: range possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 32 ref: NULL rows: 5 filtered: 11.11 Extra: Using where; Using index
1.2不能使用該索引的優化的情況
下列情況均需要掃描所有行再進行篩選,因為多列索引的第一列沒有在條件中。
1)col2 常量或范圍;
2)col3 常量或范圍;
3)col2、col3 常量或范圍;
說明:由于沒有custid的條件需要掃描9行數據。為何下面的例子中,卻使用了type: index? 因為:當索引是覆蓋索引并且可以用來滿足需要從表中得到的所有數據時,只有索引樹被掃描。在這種情況下,(EXPLAIN命令)返回記錄里的Extra字段提示Using index。此表中的orderid,orderdate為聯合主鍵。
mysql> explain select orderid,orderdate from formatting where qty=20\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: index possible_keys: NULL key: idx_custid_qty_empid key_len: 40 ref: NULL rows: 9 filtered: 11.11 Extra: Using where; Using index
我們添加一列新的列名叫version,并對其賦值,如下圖所示。
再來看一下上面會發生什么變化,此時要查詢多列避免出現覆蓋索引,我們發現沒有使用任何索引了:
mysql> desc select orderid,orderdate,empid,custid,qty,version from formatting where qty=20\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 10 filtered: 10.00 Extra: Using where
2.order by 子句中的多列索引
2.1.在不是所有的order by子句中的字段完全滿足索引,在一些情況下依然可以使用索引。
1) order by子句中的字段順序與索引字段順序一致:
create index idx_custid_qty on formatting(custid,qty);
mysql> desc select orderid from formatting order by custid,qty\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: index possible_keys: NULL key: idx_custid_qty key_len: 36 ref: NULL rows: 10 filtered: 100.00 Extra: Using index
注意:此時若使用 select * 則不會使用索引。
`mysql> desc select * from formatting order by custid,qty\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: formatting
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 9
filtered: 100.00
Extra: Using filesort`
那么為何有索引卻不能使用呢,我們結合最初的鏈接中的索引物理結構來理解:
第一種方法是直接讀取表記錄,然后根據order by 列去排序。
第二種方法是先掃描order by 上的索引,由于索引本身就是排好序的,因此根據索引的順序再讀出表的記錄即可,省去排序的過程。
我們可以看出第一種方法直接讀取表記錄就可以了.第二種方法需要先讀取索引,然后根據索引去讀取表記錄,
io會變得很隨機,因此這種方法會成本比較高,所以優化器會選擇第一種執行執行劃。
2)按照索引順序的where和order by子句依舊可以使用,但where條件中必須使用字段名> < =某常量。
mysql> desc select * from formatting where custid='A' order by qty\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: ref possible_keys: idx_custid_qty key: idx_custid_qty key_len: 32 ref: const rows: 4 filtered: 100.00 Extra: Using index condition
2.2.但是有些情況是不能使用索引的
1)同一個order by 中的字段分別有多個索引。
建立custid和empid的單獨索引。
create index idx_custid on formatting(custid) create index idx_empid on formatting(empid) mysql> desc select orderdate from formatting order by custid,empid\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 10 filtered: 100.00 Extra: Using filesort
2)where 與order by 字段的順序與索引順序不同,排序不能使用索引。例子中沒有使用聯合索引,只能在where使用單個索引。
`create index idx_custid_qty_empid on formatting(custid,qty,empid)
create index idx_qty on formatting(qty)
mysql> desc select orderdate from formatting where qty=10 order by custid,empid\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: formatting
partitions: NULL
type: ref
possible_keys: idx_qty
key: idx_qty
key_len: 4
ref: const
rows: 2
filtered: 100.00
Extra: Using index condition; Using filesort`
3) 同時包含asc與desc,排序不能使用索引,只能在where條件使用索引,排序Using filesort。
刪除其他索引 create index idx_custid_qty_empid on formatting(custid,qty,empid) mysql> explain select orderid,orderdate,empid,custid,qty,version from formatting where custid='A' order by qty desc ,empid asc\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: ref possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 32 ref: const rows: 4 filtered: 100.00 Extra: Using index condition; Using filesort
4) order by 字段中有計算,排序不能使用索引。
先來看不使用計算時,可以使用索引的情況:
mysql> explain select orderid,orderdate,empid,custid,qty,version from formatting where custid='A' order by qty\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: ref possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 32 ref: const rows: 4 filtered: 100.00 Extra: Using index condition
增加計算后,排序不能使用索引,需要Using filesort
mysql> explain select orderid,orderdate,empid,custid,qty,version from formatting where custid='A' order by qty+1\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: ref possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 32 ref: const rows: 4 filtered: 100.00 Extra: Using index condition; Using filesort
5)ORDER BY 和 GROUP BY的表達式不同,排序不能使用索引。
先來看下ORDER BY 和 GROUP BY的表達式相同情況,是不需要Using filesort。
`mysql> explain select custid,qty from formatting group by custid,qty order by custid,qty\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: formatting
partitions: NULL
type: index
possible_keys: idx_custid_qty_empid
key: idx_custid_qty_empid
key_len: 40
ref: NULL
rows: 10
filtered: 100.00
Extra: Using index`
但是,如果不同的情況排序是不能使用索引的,需要再一次Using filesort。
mysql> explain select custid,qty from formatting group by custid,qty order by qty\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: formatting partitions: NULL type: index possible_keys: idx_custid_qty_empid key: idx_custid_qty_empid key_len: 40 ref: NULL rows: 10 filtered: 100.00 Extra: Using index; Using temporary; Using filesort
6) join的表ORDER BY字段來自不同的表只能使用主表的索引。
如果查詢需要關聯多張表,則只有當ORDER BY子句引用的字段全部為第一個表時,才能使用索引做排序。此時需要注意,在inner join時,優化器將哪張表選做主表,就只能使用此表的索引。
其中一定要滿足order by 在單表查詢中的要求,若去掉T1.empid=2,則同2.1中1情況不能使用排序索引。
使用主表的排序:
mysql> desc select * from formatting t1 left join emp t2 on t1.empid=t2.empid WHERE T1.empid=2 order by T1.custid \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 partitions: NULL type: ref possible_keys: idx_empid_custid key: idx_empid_custid key_len: 4 ref: const rows: 2 filtered: 100.00 Extra: Using index condition *************************** 2. row *************************** id: 1 select_type: SIMPLE table: t2 partitions: NULL type: ref possible_keys: idx_empid key: idx_empid key_len: 5 ref: const rows: 1 filtered: 100.00 Extra: NULL
使用第二張表排序,需要Using filesort:
mysql> desc select * from formatting t1 left join emp t2 on t1.empid=t2.empid WHERE T1.empid=2 order by T2.empname \G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 partitions: NULL type: ref possible_keys: idx_empid_custid key: idx_empid_custid key_len: 4 ref: const rows: 2 filtered: 100.00 Extra: Using temporary; Using filesort *************************** 2. row *************************** id: 1 select_type: SIMPLE table: t2 partitions: NULL type: ref possible_keys: idx_empid key: idx_empid key_len: 5 ref: const rows: 1 filtered: 100.00 Extra: NULL
對于上述去掉where條件情況可以如下優化語句:
優化前語句:
SELECT * FROM a LEFT JOIN b ON a.id=b.a_id ORDER a.id DESC
優化后語句:
SELECT * FROM a LEFT JOIN b ON a.id=b.a_id JOIN (SELECT id FROM a ORDER BY id DESC) a_order ON a.id = a_order.id