轉載於: https://blog.51cto.com/lee90/2058185
什么是MRR?
MRR:multi range read。
本質: MRR 在本質上是一種用空間換時間的算法
不好解釋,先來看個例子:
select * from tb where key_column = x
在沒有MRR的情況下,它是這樣得到結果的:
1. select key_column, pk_column from tb where key_column=x order by key_column ---> 假設這個結果集是t
2. for each row in t ; select non_key_column from tb where pk_column = pk_column_value。(在Oracle里第2步叫回表?)
在有MRR的情況下,它是這樣執行的:
1. select key_column, pk_column from tb where key_column = x order by key_column ---> 假設這個結果集是t
2. 將結果集t放在buffer里面(直到buffer滿了),然后對結果集t按照pk_column排序 ---> 假設排序好的結果集是t_sort
3. select non_key_column fromtb where pk_column in (select pk_column from t_sort)
兩者的區別主要是兩點:
1. 沒有MRR的情況下,隨機IO增加,因為從二級索引里面得到的索引元組是有序,但是他們在主鍵索引里面卻是無序的,所以每次去主鍵索引里面得到non_key_column的時候都是隨機IO。(如果索引覆蓋,那也就沒必要利用MRR的特性了,直接從索引里面得到所有數據)
2. 沒有MRR的情況下,訪問主鍵索引的次數也會增加。沒有MRR的情況下,二級索引里面得到多少行,那么就要去訪問多少次主鍵索引(也不能完全這樣說,因為MySQL實現了BNL),而有了MRR的時候,次數就大約減少為之前次數t/buffer_size。
所以說MRR主要解決的就是這兩個問題。
此外,MRR還可以將某些范圍查詢,拆分為鍵值對,以此來進行批量的數據查詢。這樣做的好處是可以在拆分過程中,直接過濾一些不符合查詢條件的數據。
如:
官方文檔:https://dev.mysql.com/doc/refman/5.7/en/mrr-optimization.html
> SELECT * FROM t WHERE key_part1 >=1000 AND key_part1 < 2000 AND key_part2 = 1000;
表t有(key_part1,key_part2)的聯合索引,因此索引根據key_part1,key_part2的位置關系進行排序。若沒有MRR,此時查詢類型為Range,SQL優化器會先將key_part1大於1000且小於2000的數據都取出來,即便key_part2不等於1000。取出后再根據key_part2的條件進行過濾。這會導致無用的數據被取出。
如果啟用MRR優化器會使性能有巨大的提升,優化器會先將查詢條件拆分為(1000,1000),(1001,1000),(1002,1000)....(1999,1000) 最后再根據這些拆分出的條件進行數據的查詢。
是否啟用MRR優化,可以通過參數optimizer_switch中的flag來控制。當MRR為on時,表示啟用MRR優化。mrr_cost_based表示是否通過costbased的方式來選擇是否啟用mrr。若設置mrr=on,mrr_cost_based=off,則總是啟用MRR優化。如下:
> SET GLOBAL optimizer_switch='mrr=on,mrr_cost_based=off';
參數read_rnd_buffer_size用來控制鍵值的緩沖區大小。當大於該值時,則執行器對已經緩存的數據根據RowID進行排序,並通過RowID來取得行數據,該值默認是256KB
>show VARIABLES like 'read_rnd_buffer_size';
+----------------------+---------+
| Variable_name | Value |
|----------------------+---------|
| read_rnd_buffer_size | 262144 |
+----------------------+---------+