公眾號文章匯總
774
2025-03-31
InnoDB為了盡可能的讓用戶經常讀取的數據都放在內存中,以減少磁盤的IO次數,提高讀性能,加入了預讀特性。這個特性會將用戶很有可能使用到的數據預先加載到buffer pool中,當用戶使用到這個數據時,就不必再從磁盤上讀入,從而提升了數據的讀取性能。
然而一般數據庫存儲的數據量都會遠遠大于內存,innodb不可能全部都加載到內存中,對加載數據的選擇,決定了預讀是否能夠有效提升整體性能。
預讀的模式
Innodb一共提供了兩種預讀模式:線性預讀和隨機預讀。兩種模式使用不同的算法來預測用戶可能用到的數據,并把這些數據異步地從磁盤中加載到buffer pool中,以備使用。
·???????? 線性預讀
線性預讀是以extend為單位的。通過判斷當前的extend中的數據是否是連續訪問的,并以此來預測是否有必要把下一個extend提前從磁盤文件中讀取出來,加載到buffer pool中。本文主要分析線性預讀的使用和原理。
·???????? 隨機預讀
隨機預讀針對的是當前extend,通過判斷當前extend中的page被讀取的次數,來預測是否有必要把當前extend的數據加載到buffer pool中。在MySQL 5.5之后基本已經廢棄了隨機預讀,因此后面的版本隨機預讀默認是關閉的。
預讀的使用
MySQL5.5之后的版本安裝好以后默認使用的是線性預讀模式。因此無需額外配置就能使用。
與線性預讀相關的配置參數,是innodb_read_ahead_threshold,這個參數表示:當一個extend中,大于等于innodb_read_ahead_threshold個page是被連續訪問的時候,就預先加載下一個extend的數據。默認值是56,最大值64,最小值0。如果設置為0,則關閉線性預讀。(除非顯式打開隨機預讀,否則如果關閉了線性預讀,也不會自動打開隨機預讀,此時整個預讀功能就關閉了)。
線性預讀的原理
從上面的說明中,大家可能大概了解了線性預讀的使用,然而對于像筆者一樣的初學者來說,很多概念其實并沒有真正了解其中的含義,因此也就不能指導我們更好的使用預讀功能。比如:什么叫做“數據是被連續訪問的”,為什么innodb_read_ahead_threshold的最大值是64等等,下面通過對線性預讀的源碼,來看這個深入理解功能是如何運作的。
線性預讀入口函數在:storage/innobase/buf目錄下的buf0rea.cc文件中,函數名:buf_read_ahead_linear。
·???????? Extend的邊界
我們知道innodb對數據的組織,是:tablespace -> segment -> extend -> page -> row這樣的組織順序。1個extend的大小固定為1M,1個page的默認大小是16K,那么1個extend最大可以容納64個Page,并且extend中的page一定是連續存儲的。這也就是為什么innodb_read_ahead_threshold的最大值是64,同時也可以看到,對數據連續訪問的判斷,也僅限制在一個extend中,不會跨越extend.
那么代碼中如何區分extend的呢(此處吐槽一下MySQL源代碼,如果不事先了解這些概念就,會發現代碼中連extend這個關鍵字都沒有)
請看圖1,532和534行就是mysql獲取當前extend的邊界。
圖1 找到當前extend
圖2 gdb調試結果
通過圖2的GDB調試中可以看到,其中buf_read_ahead_linear_area為64(因為page大小是可以更改的,因此并不是一個常量,而是計算出來的)。Page number是201,通過532和534兩行簡單的就計算出extend的上下邊界。上圖page 201所在的extend,就是由Page 192到Page 255(這里要減1,一共64個)這些Page組成的(這里居然不封裝為一個函數。。。)。
同時,從537行的判斷可以知道,如果page不是extend的上邊界或者下邊界,是不執行預讀的。也就是說,只有讀取到了extend的上、下邊界page,才有可能觸發預讀。
·???????? 預讀不會跨域表
如圖3代碼所示,innodb會獲取page所在tablespace,并且判斷extend是否跨越了tablespace(561行,extend上限page大于space_size),如果跨越,則不會執行預讀。
圖3 對tablespace的判斷
·???????? 數據被連續訪問的意義
如圖4所示,這里就是innodb判斷數據是否是被連續訪問的算法。
圖4 判斷連續訪問的算法
590行,這里就是innodb_read_ahead_threshold產生作用的地方,如圖5和圖6所示:
圖5 將innodb_read_ahead_threshold設置為2
圖6 serv_read_ahead_threshold的值為2
innodb_read_ahead_threshold表示了只要2個page是被連續訪問的,就進行預讀,那么此處就是計算出能夠允許(64 - 2)=62個page是沒有被連續訪問的。另外與BUF_READ_AHEAD_AREA(buf_pool)取最小值的原因是page可能不是16K,那么每個extend的page數量就一定是64了。同時,從這個邏輯也能分析出,如果page不是1K,那么innodb_read_ahead_threshold就沒有作用了。
接下來先介紹613和614行的buf_page_is_accessed函數,從圖7中可以看到,所謂的順序依據,實際上是page的access time,也就是上一次訪問時間。
圖7 buf_page_is_accessed函數
再看582~586行:low是extend的第一個page位置,如果page是extend的第一個page,那么順序就是遞減(-1),如果是最后一個,那么就是遞增(1)。
那么,595~630行的的循環邏輯相信任何一個編程者都能理解:
如果當前page是extend的第一個,那么就看后面的63個page中,相鄰page訪問時間遞減的個數,是否≥innodb_read_ahead_threshold個。如果是,則認為可以執行線性預讀,加載上一個extend。否則不進行預讀。
反之如果當前page是extend的最后一個,那么久看前面的63個page中,相鄰page訪問時間遞增的個數,是否≥innodb_read_ahead_threshold個。
小結:
線性預讀其背后的邏輯,實際上就是:如果我們在讀第一份數據的時候發現,之前的數據讀取順序是從后向前讀的,那么我們就把前一個區域的數據都加載進來。如果在讀最后一份數據的時候發現,之前的數據讀取順序是從前向后讀的,那么我們就把后面一個區域的數據加載進來。而innodb_read_ahead_threshold,就是我們認為滿足這個順序的程度。
通過以上的解讀,我們也發現,只有在對一個表進行頻繁的的順序、或者逆序查詢時,預讀才有較好的效果。在隨機讀取的情況下,線性預讀并不能產生太大作用。
RDS-MYSQL 云數據庫 MySQL RDS MySQL MySQL
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。