iT邦幫忙

2021 iThome 鐵人賽

DAY 17
0
自我挑戰組

那些Mysql我不知道的事系列 第 17

索引合併(index merge)

一般來說,Mysql只會為單一索引生成掃描區間,但還是有特殊情況會為多個索引生成掃描區間。
這種為多個索引生成掃描區間,也就是使用多個索引來完成一次查詢的方法稱為索引合併(index merge)。

為了故事順利發展,先把之前的例子再貼一次

mysql> create table single_table(
    -> id int not null auto_increment,
    -> key1 varchar(10),
    -> key2 int,
    -> key3 varchar(100),
    -> key_part1 varchar(100),
    -> key_part2 varchar(100),
    -> key_part3 varchar(100),
    -> common_field varchar(100),
    -> primary key (id),
    -> key idx_key1 (key1),
    -> unique key uk_key2 (key2),
    -> key idx_key3 (key3),
    -> key idx_key_part(key_part1, key_part2, key_part3)
    -> ) engine=InnoDB charset=utf8;
Query OK, 0 rows affected, 1 warning (0.05 sec)

為id列建立的聚簇索引
為key1列建立的idx_key1二級索引
為key2列建立的uk_key2二級索引,且是唯一
為key3列建立的idx_key3二級索引
為key_part1、key_part2、key_part3列建立的idx_key_part二級索引,這也是個聯合索引

intersection索引合併

select * from single_table where key1 = 'a' and key3 = 'b';

當然你可以透過key1的idx_key1二級索引或key3的idx_key3二級索引先去查詢獲得紀錄並回表操作後去比對key3 = 'b'或key1 = 'a'的紀錄,這樣子的全表掃描的方式也是可以。
但如果我們同時使用idx_key1二級索引及idx_key3二級索引,也就是在idx_key1樹掃描key1值在['a','a']區間中的紀錄,且同時在idx_key3樹掃描key3值在['b','b']區間中的紀錄,然後再根據各自的結果找到id值相同的紀錄再去回表操作,這樣大大的降低回表操作的負擔,提升了效能。

另外如果使用intersection索引合併則要求key1值在['a','a']區間中的紀錄及key3值在['b','b']區間中的紀錄必須是按照主鍵值排序!
因為
第一個從兩個有序集合中取交集遠比兩個無序集合容易多。
再來第二個如果id值是有序排列的,則這些id值在回表操作的時候就不再是單純的隨機I/O,進而提高效率。

舉個實際例子來看一下
假設idx_key1的掃描區間['a','a']中二級索引紀錄的id值是排好序的,順序是1、3、5
idx_key3的掃描區間['b','b']中二級索引紀錄的id值也是排好序的,順序是2、3、4
那麼使用intersection索引合併來查詢的過程如下:
步驟1
先從idx_key1的掃描區間['a','a']中取出第一筆二級索引紀錄,該紀錄的主鍵值為1
然後從idx_key3的掃描區間['b','b']中取出第一筆二級索引紀錄,該紀錄的主鍵值為2
因為1<2,紀錄不同,所以直接把小的那筆紀錄(1)捨棄

步驟2
再從idx_key1的掃描區間['a','a']中取出第一筆二級索引紀錄,該紀錄的主鍵值為3
跟本來步驟1中的主鍵值2做比較
因為2<3,紀錄不同,所以直接把小的那筆紀錄(2)捨棄

步驟3
再idx_key3的掃描區間['b','b']中取出第一筆二級索引紀錄,該紀錄的主鍵值為3
跟本來步驟2中的主鍵值3做比較
因為3=3,交集成功,去回表得到完整的使用者紀錄將其發送給用戶端

步驟4
再從idx_key1的掃描區間['a','a']中取出第一筆二級索引紀錄,該紀錄的主鍵值為5
然後從idx_key3的掃描區間['b','b']中取出第一筆二級索引紀錄,該紀錄的主鍵值為4
因為4<5,紀錄不同,所以直接把小的那筆紀錄(4)捨棄

步驟5
從idx_key3的掃描區間['b','b']中要再取一筆二級索引紀錄,發現沒有了,結束查詢

如果在使用某個二級索引查詢時,取得的紀錄不是照主鍵值排序,則不能使用intersection索引合併。
如下:

select * from single_table where key1 > 'a' and key3 = 'b';

因為從idx_key1的掃描區間['a',+無限大]獲得的紀錄並不是按照主鍵值排序的,所以不能使用intersection索引合併。

select * from single_table where key1 = 'a' and key_part1 = 'a';

對於idx_key_part二級索引來說,先照key_part1排序,相同再照key_part2排序。
所以key_part1 = 'a'不是照主鍵值排序,就不能使用intersection索引合併。

union索引合併

select * from single_table where key1 = 'a' or key3 = 'b';

使用key1的idx_key1二級索引或key3的idx_key3二級索引去查詢,因為其區間為[-無限大,+無限大],所有的紀錄都要再去回表操作,比不用索引的全表掃描還慢。
所以這時我們可以跟intersection索引合併一樣
同時使用idx_key1二級索引及idx_key3二級索引去各自得到結果,只是這邊變成是去掉重複的值,然後再回表得到完整使用者紀錄,這就是union索引合併。
也跟intersection一樣各個索引的結果必須是有序的。

sort-union索引合併
由於union索引合併的條件太過嚴苛(各個索引中掃描到的紀錄都必須是有序的),所以有了sort-union索引合併。
看以下的例子:

select * from single_table where key1 < 'a' or key3 > 'z';

我們可以先把根據key1 < 'a'條件得到的紀錄做排序
再將根據key3 > 'z'得到的紀錄做排序
接下來就跟union合併操作一樣辣
這個就叫做sort-union索引合併(比union索引合併多了一個sort的過程)


上一篇
存取方法
下一篇
連接的原理(基本概念、內連接與外連接)
系列文
那些Mysql我不知道的事30

尚未有邦友留言

立即登入留言