在探索數據庫優化的廣闊領域中,我們不可避免地會遇到一系列獨特的概念和技術。其中之一就是MySQL的多范圍讀取(Multi-Range Read, MRR)。
這種技術為我們提供了在處理大量數據時提高查詢效率的強大手段。它通過改變數據檢索的順序,并利用操作系統緩存進行預讀,從而顯著減少I/O操作數量,提高查詢速度。本文將深入探討MRR的內部工作原理,以及如何在日常數據庫管理中有效地應用這種技術。
什么是MRR
MRR 是優化器將隨機 IO 轉化為順序 IO 以降低查詢過程中 IO 開銷的一種手段。
了解MRR之前,我們先來了解下「回表」。
回表是MySQL在執行查詢時的一個步驟,它通常發生在使用索引進行搜索之后。當MySQL在索引中找到了需要的數據,但這些數據并不完全滿足查詢需求時(比如,索引沒有包含所有需要的列),MySQL就需要回到主表中去獲取完整的行數據,這個過程就被稱為"回表"。
舉例來說,如果查詢語句中有一些列沒有被包含在索引中,那么即使從索引中能查到部分信息,也還需要回到原始表中獲取其他列的信息,這就是所謂的"回表"操作。為了提高查詢效率,我們可以盡量減少回表操作,例如通過使用「覆蓋索引(Covering Index)」。
我們知道二級索引是有回表的過程的,由于二級索引上引用的主鍵值不一定是有序的,因此就有可能造成大量的隨機 IO,如果回表前把主鍵值在內存中給它排一下序,那么在回表的時候就可以用順序 IO 取代原本的隨機 IO。
在沒有MRR的情況下,MySQL會按照索引順序來訪問行數據,而索引順序并不一定與磁盤上的物理存儲順序一致,這就可能產生大量的隨機磁盤I/O。
當啟用MRR后,MySQL會先按照索引掃描記錄,但并不立即去獲取行數據,而是將每個需要訪問的行位置(例如主鍵)保存到一個緩沖區中。
然后,MySQL會根據這些行位置,按照物理存儲的順序(通常也就是主鍵順序)去獲取行數據。這樣就能避免大量的隨機I/O,因為數據現在是按照它們在磁盤上的物理存儲順序被訪問的。
比如,當我執行這個語句時:
select * from t1 where a>=1 and a<=100;
主鍵索引是一棵B+樹,在這棵樹上,每次只能根據一個主鍵id查到一行數據。因此,回表肯定是一行行搜索主鍵索引的,基本流程如圖所示。
如果隨著a的值遞增順序查詢的話,id的值就變成隨機的,那么就會出現隨機訪問,性能相對較差。雖然“按行查”這個機制不能改,但是調整查詢的順序,還是能夠加速的。
因為大多數的數據都是按照主鍵遞增順序插入得到的,所以我們可以認為,如果按照主鍵的遞增順序查詢的話,對磁盤的讀比較接近順序讀,能夠提升讀性能。
這,就是MRR優化的設計思路。此時,語句的執行流程變成了這樣:
-
根據索引a,定位到滿足條件的記錄,將id值放入
read_rnd_buffer
中。 -
將read_rnd_buffer中的id進行遞增排序。
-
排序后的id數組,依次到主鍵id索引中查記錄,并作為結果返回。
這里,read_rnd_buffer
的大小是由read_rnd_buffer_size
參數控制的。
如果步驟1中,read_rnd_buffer
放滿了,就會先執行完步驟2和3,然后清空read_rnd_buffer
。之后繼續找索引a的下個記錄,并繼續循環。
下面兩幅圖就是使用了MRR優化后的執行流程和explAIn結果。
從explain結果中,我們可以看到Extra字段多了「Using MRR」,表示的是用上了MRR優化。而且,由于我們在read_rnd_buffer
中按照id做了排序,所以最后得到的結果集也是按照主鍵id遞增順序的,也就是與圖1結果集中行的順序相反。
MRR能夠提升性能的核心在于,這條查詢語句在索引a上做的是一個范圍查詢(也就是說,這是一個多值查詢),可以得到足夠多的主鍵id。這樣通過排序以后,再去主鍵索引查數據,才能體現出“順序性”的優勢。
簡單來說:MRR 的核心思想就是通過把「隨機磁盤讀」,轉化為「順序磁盤讀」,從而提高了索引查詢的性能。
順序讀帶來了兩個好處:
-
磁盤和磁頭不再需要來回做機械運動。
-
可以充分利用磁盤預讀。
所謂的磁盤預讀,比如說在客戶端請求一頁的數據時,可以把后面幾頁的數據也一起返回,放到數據緩沖池中,這樣如果下次剛好需要下一頁的數據,就不再需要到磁盤讀取。這樣做的理論依據是計算機科學中著名的局部性原理:當一個數據被用到時,其附近的數據也通常會馬上被使用。
MRR 在本質上是一種用「空間換時間」的做法。
MySQL 不可能給你無限的內存來進行排序,這塊內存的大小就由參數read_rnd_buffer_size
來控制,如果read_rnd_buffer
滿了,就會先把滿了的 rowid 排好序去磁盤讀取,接著清空,然后再往里面繼續放 rowid,直到 read_rnd_buffer
又達到 read_rnd_buffe
配置的上限,如此循環。
MRR如何使用
MRR相關參數如下:
//如果你不打開,是一定不會用到 MRR 的。
set optimizer_switch='mrr=on';
set optimizer_switch ='mrr_cost_based=off';
set read_rnd_buffer_size = 32 * 1024 * 1024;
mrr_cost_based: on/off
,則是用來告訴優化器,要不要基于使用 MRR 的成本,考慮使用 MRR 是否值得(cost-based choice),來決定具體的 SQL 語句里要不要使用 MRR。
很明顯,對于只返回一行數據的查詢,是沒有必要 MRR 的,而如果你把 mrr_cost_based
設為 off,那優化器就會通通使用 MRR,這在有些情況下是很 stupid 的,所以建議這個配置還是設為 on,畢竟優化器在絕大多數情況下都是正確的。
通過本文我們可以了解到,MySQL的多范圍讀取(MRR)優化提供了一個高效的方式來處理和加速查詢性能。特別是在處理大量數據、聯接操作或者需要處理大量行的復雜查詢時,MRR都會展現出其強大的優勢。
然而,我們也要注意到,不是所有情況下啟用MRR都會提升性能,一些具體的場景可能會產生額外的磁盤I/O開銷。因此,理解其工作原理并合適地運用在恰當的場景,才是有效使用這個優化策略的關鍵。