之前寫的《mysql B+Tree索引的一點理解》一文中,介紹了MySQL在使用輔助索引的原理,通過輔助索引進行回表不難理解就相當於Oracle的index skip scan.但是mysql5.6版本中推出了mrr功能,其實就是將隨機訪問的數據,通過內部機制緩存到線程內存read_rnd_buffer_size中,然后進行排序,排序后的數據再訪問主鍵索引,將隨機訪問改變為了順序訪問。近似理解為Oracle中的index range scan
一:優點
1.磁盤和磁頭不再需要來回做機械運動
如果沒有這個功能,那么每獲取一個輔助索引的葉子塊就會遍歷一下主鍵,找到對應的數據--該過程我們又稱為回表。
mrr功能,將這些輔助索引掃描后的數據同一進行緩存,然后一次性訪問主鍵索引,然后找到對應的數據,這樣就大大減少了訪問數據塊的數量
2.可以充分利用磁盤預讀
mysql數據庫有一個預讀功能,也就是訪問一個頁的數據時,將臨近頁也會加載到內存中,剛好需要下一頁的數據時就不再需要進行物理IO
二:案例演示
說明:本測試在mysql 5.7.35中進行測試。
1.表結構
Create Table: CREATE TABLE `salaries` (
`emp_no` int(11) NOT NULL,
`salary` int(11) NOT NULL,
`from_date` date NOT NULL,
`to_date` date NOT NULL,
PRIMARY KEY (`emp_no`,`from_date`),
KEY `idx_salaries_salary` (`salary`),
CONSTRAINT `salaries_ibfk_1` FOREIGN KEY (`emp_no`) REFERENCES `employees` (`emp_no`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
2.優化器參數
root@localhost [employees]>show variables like '%optimizer_switch%'\G;
*************************** 1. row ***************************
Variable_name: optimizer_switch
Value: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=on,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on,prefer_ordering_index=on
1 row in set (0.00 sec)
3.查詢SQL並查看執行計划
從這里並沒有發現該執行步驟使用了mrr功能,還是每行檢索之后訪問主鍵索引,然后進行回表
root@localhost [employees]>explain select * from salaries where salary>10000 and salary<40000;
+----+-------------+----------+------------+-------+---------------------+---------------------+---------+------+-------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------------+---------------------+---------+------+-------+----------+-----------------------+
| 1 | SIMPLE | salaries | NULL | range | idx_salaries_salary | idx_salaries_salary | 4 | NULL | 21450 | 100.00 | Using index condition |
+----+-------------+----------+------------+-------+---------------------+---------------------+---------+------+-------+----------+-----------------------+
4.關閉MySQL成本控制
root@localhost [employees]>set optimizer_switch='mrr_cost_based=off';
Query OK, 0 rows affected (0.00 sec)
root@localhost [employees]>show variables like '%optimizer_switch%'\G;
*************************** 1. row ***************************
Variable_name: optimizer_switch
Value: index_merge=on,index_merge_union=on,index_merge_sort_union=on,index_merge_intersection=on,engine_condition_pushdown=on,index_condition_pushdown=on,mrr=on,mrr_cost_based=off,block_nested_loop=on,batched_key_access=off,materialization=on,semijoin=on,loosescan=on,firstmatch=on,duplicateweedout=on,subquery_materialization_cost_based=on,use_index_extensions=on,condition_fanout_filter=on,derived_merge=on,prefer_ordering_index=on
1 row in set (0.00 sec)
5.再次執行查看執行計划
這時我們發現,執行計划已經使用了mrr功能,對輔助索引數據進行緩存之后,一次回表,
root@localhost [employees]>explain select * from salaries where salary>10000 and salary<40000;
+----+-------------+----------+------------+-------+---------------------+---------------------+---------+------+-------+----------+----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------------+---------------------+---------+------+-------+----------+----------------------------------+
| 1 | SIMPLE | salaries | NULL | range | idx_salaries_salary | idx_salaries_salary | 4 | NULL | 21450 | 100.00 | Using index condition; Using MRR |
+----+-------------+----------+------------+-------+---------------------+---------------------+---------+------+-------+----------+----------------------------------+
但是上面基於成本MySQL為什么沒有使用這種方式呢?
顯然上面回表效率是高效的,但是MySQL優化器對於MRR功能又是相當的悲觀。還是盡可能的選擇索引掃描回表。這是我們需要注意的地方
6.mysql5.7版本之后支持hint
mysql5.7版本之后,我們可以使用hint的方式來強制SQL走mrr
root@localhost [employees]>explain select /*+ mrr(salaries) */ * from salaries where salary>10000 and salary<40000;
+----+-------------+----------+------------+-------+---------------------+---------------------+---------+------+-------+----------+----------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+----------+------------+-------+---------------------+---------------------+---------+------+-------+----------+----------------------------------+
| 1 | SIMPLE | salaries | NULL | range | idx_salaries_salary | idx_salaries_salary | 4 | NULL | 21450 | 100.00 | Using index condition; Using MRR |
+----+-------------+----------+------------+-------+---------------------+---------------------+---------+------+-------+----------+----------------------------------+