運維日記| MySQL/Oracle深度解析之一:邏輯讀

運維日記| MySQL/Oracle深度解析之一:邏輯讀

原創: 呂海波 新運維新數據 今天

各位新朋友~記得先點藍字關注我哦~

前言
THE FIRST

比較數據庫優劣、異同的文章有很多了,使用壓測工具,進行不同壓力下的測試,就能大致上比較出來哪種數據庫是“最快”的數據庫。但從有經驗的數據架構、DBA等專業人士角度,僅僅“最快”是不夠的,數據庫是十分複雜的體系,要考慮方方面面的問題。

本文從“競爭”的角度,使用調試技術,在MySQL和Oracle的代碼世界中暢遊,像使用顯微鏡一樣,以“最近”的距離,分析兩種數據庫由於代碼設計的不同,而造成的競爭與鎖機制的異同。

數據庫的競爭與鎖機制,是數據庫最爲複雜的部分,不可能在一篇短短的文章中描述完,本文以“邏輯讀”爲樣本,從細枝末節中對比MySQL、Oracle的優劣、異同。邏輯讀是數據庫最基礎的操作,也是最繁忙的操作。

我們先從MySQL入手,這裏的研究對象當然是MySQL的InnoDB引擎。其他常用引擎比如Myisam根本沒有緩存,沒有數據庫層面的邏輯讀。

再說一下調試技術,它不同於直接一上來就開始的源碼閱讀,調試技術重點在於要讀“活”着的源碼。我們不去看磁盤文件中靜止的.c、.cpp、.h等文件,而是要把程序運行起來,再結合源碼,觀察它運行起來的行爲,理解源碼其中的細枝末節。

有了對細節的探索,可以像他一樣:

否則,只能像他一樣:

第一節
基礎知識介紹:MySQL中的HASH表與CBC Latch
MySQL的邏輯讀,網上有相關函數,從buf_page_get_gen函數開始,一直到MySQL8都是這樣。不過相比5.7,這個函數在MySQL8中有了很大的改動。我們後面的所有分析,都是針對MySQL 8。

數據頁或塊的緩存,在MySQL中稱爲Buffer Pool,在Oracle中稱爲Buffer Cache,下面統一簡稱數據緩存。無論MySQL還是Oracle,數據緩存的管理方式類似,如下圖:

其實不只MySQL、Oracle,此圖適用於所有主流關係型數據庫的數據緩存管理。

在MySQL中,HASH表由hash_cell_t型的結構組成,每一個Bucket,都是一個hash_cell_t型的結構變量。hash_cell_t的定義如下:

plugin/innodb_memcached/innodb_memcache/include/innodb_utility.h的126行中你可以找到它的定義。

我建議你打開它看一看,這樣,你也是讀過MySQL源碼的人了,酷不酷。

每一個HASH Bucket都是一個void型的指針,也就是內存地址。在64位系統中,佔8字節:

每一個HASH Bucket後,都是一個鏈表,就是如下的部分:

Oracle中稱其爲Cache Buffer Chains,簡稱CBC鏈。MySQL中沒有專門的稱呼,也稱爲CBC鏈吧,反正和Oracle都是一樣的東西。

MySQL中,鏈表中的每個Node,都是buf_page_t型的類,它的定義在storage/innobase/include/buf0buf.h的1156行,這個就比較複雜了,如果不準備入調試技術的坑,只是看個熱鬧,我就不建議打開看了。它主要包含文件號、頁號、下一個Node的地址、Buffer地址等成員變量。

數據庫在HASH Table中搜索Buffer Pool中某個Buffer的過程,我們就不詳細說了,有過很多資料講述這一過程。MySQL中的相關代碼在torage/innobase/include/buf0buf.ic的948行buf_page_hash_get_low函數中,代碼量並不算複雜,讀一下可以使你更拉轟。Oracle中參考《Oracle內核技術揭密》吧,第三章專門講這個。

無論MySQL、Oracle,在HASH Table中搜索Buffer的過程都需要鎖的保護。Oracle中稱這個鎖爲Cache Buffer Chains Latch,簡稱CBC Latch,MySQL中也沒有專門的稱呼,我們也稱它爲MySQL的CBC Latch吧。

好了,基礎知識介紹完畢。下面進入本次分享的最主要內容了,近距離分析MySQL、Oracle的異同。下面先從CBC Latch的數量開始。

第二節
MySQL CBC Latch數量造成的競爭

Oracle中CBC Latch數量是很多的(由隱藏參數“_db_block_hash_buckets”計算得到),缺省情況下根據你所設定的Buffer cache大小設定,至少幾千個。Buffer Cache越大,CBC Latch數量也越多。

MySQL中呢,CBC Latch的數量就少的可憐了,16個(準確來說,是一個Buffer Pool Instance 16個。一個Buffer Pool Instance就是一個Buffer Pool的子池)。而且,這個數字並不會隨着Buffer Pool加大而增大。再大的Buffer Pool,它也是16個。

我猜測,可能是MySQL邏輯讀時都是以共享方式持有CBC Latch。因此,MySQL的開發者認爲CBC Latch的競爭不會太激烈,所以CBC Latch數量不必太多。
雖然邏輯讀都是以共享的模式持有CBC Latch,但物理讀可就要以獨佔方式持有CBC Latch了。如果只有16個CBC Latch的話,可以想像,在物理I/O比較多時,競爭一定是激烈的。

做個測試,驗證一下邏輯讀、物理讀時CBC Latch的阻塞情況。測試非常簡單,因爲CBC Latch鎖只有16個,只要找17個頁,這17個頁中,必然有兩個頁共用一個CBC Latch。

我使用下面的存儲過程,向一個表中插入了50行。每一行都佔6000字以上,50行數據,使用的頁數量絕對在17個以上,一定有兩個頁用同一個CBC Latch保護。

可以得到T1表所有頁的HASH值,HASH值相同的,就是使用同一個CBC Latch的頁。在我的測試環境,顯示的結果id1爲1行所在頁,和id1爲34的行所在頁,共用一個CBC Latch,測試過程如下圖:

簡單說一下這個測試,先開始一個邏輯讀(查詢多次id1=1的頁),使用gdb,當線程獲得id1=1頁上的CBC Latch後停住(也就是在執行完pfs_rw_lock_s_lock_func()函數後停住)。

接下來再開啓一個連接,查詢id1=34的行。Id1=34的行所在頁之前沒被讀過,它是一次物理讀,它會被前面的邏輯讀阻塞。然後我們可以慢慢觀察MySQL出現CBC Latch阻塞後的情況。首先查看show processlist中的狀態:

紅框中的Session,正在執行id1=34的查詢,它已經被阻塞了。但你無法得知它被阻塞,它的狀態還是Query和Statistics。Oracle中可不是這樣,你可以在v$session中看到Session的準確狀態,是WAITING、WAITED SHORT TIME和ACTIVE、INACTIVE。

MySQL中除了show processlist,還可以在show engine innodb status中看到更多信息:

在“show engine innodb status”中可以看到CBC Latch出現阻塞。上圖紅框中的信息說明有一個線程已經被阻塞了,它在等待X-LOCK鎖。

但是show engine innodb status的結果不是標準的二維表,而且這裏只顯示有阻塞,阻塞的次數、時間都不知道。而且CBC Latch的持有時間都是很快的,多了一小會兒競爭時間,你也感覺不到。可能爲讓一次毫秒級耗時的邏輯讀,增加幾十、或幾百微秒。

Oracle中可以SQL統計計算CBC Latch被調用的總次數、阻塞次數、阻塞時間、……等等信息。通過這些信息,美創運維中心中有經驗的DBA一眼就可以看出來,CBC Latch是否競爭過於激烈。

我們還回到CBC Latch的數量問題上來。我們可以得到一點結論了,在MySQL中,因爲只有16個CBC Latch鎖,平均17個頁會有兩個頁共用一個CBC Latch鎖,也就是說平均17次物理讀,會有一次CBC Latch競爭。

我們展開說一下,MySQL其實不是SSD友好型數據庫。SSD大大提高了物理讀的速度,但由於CBC Latch數量過少,在出現物理讀時,較容易引發阻塞。

而且你很難察覺這些阻塞。它只是多佔了點CPU、讓你本來可以更快的SQL,慢了一點。至於慢了多少就很難說了,我粗略的說一下,物理讀稍多的情況下有大概5%到10%的性能損耗。有沒有可能解決這種問題呢?

當然有,就是增加CBC Latch的數量。當你使用了SSD、特別是高性能的pci-e槽的SSD時,一定要記得增加CBC Latch的數量。

MySQL使用參數“innodb_page_hash_locks”控制CBC Latch的數量,該參數的缺省值是16。修改它也是很簡單的事。但是,等等,爲什麼從來沒聽人說過要修改這個參數呢?網上搜索了一下,也鮮有針對這個參數的介紹:

你可以換種搜索引擎,反正我用某度搜索“innodb_page_hash_locks”,啥都沒搜到。並不是它不重要,剛纔我們說了,是它造成的競爭很難察覺,大家不知道而已。

那麼這個參數具體應該設爲什麼值呢?這要結合你的系統、主機CPU/內存等來確定。也可以參照Oracle中CBC Latch的數量,這可是經過了無數大廠驗證過的值。或者,我建議你找美創科技的專業運維團隊,爲你的系統進行健康檢查後,再決定是否需要修改、如何修改此參數的值。

如果你的Oracle數據庫,遇到了CBC Latch或其他情況的競爭,當然也可以找我們,記住一句口號:

“有情況,找美創”

接下來我們再來看看訪問Buffer頁時的情況。

第三節
MySQL與Oracle的Buffer Pin Lock比較

搜索CBC鏈、找到目標Buffer之後,就要對頁進行訪問了。訪問頁也需要在鎖的保護下進行,這個鎖在Oracle中,稱爲Buffer Pin Lock,有興趣瞭解Oracle的Buffer Pin Lock的話,可以看我的那本《Oracle內核技術揭密》第三章3.1.2小節。MySQL中的“Buffer Pin Lock”,限於篇幅,原理咱們就不展開介紹了。我們只說Buffer Pin Lock的相容、阻塞規則。

MySQL的Buffer Pin Lock規則很簡單:
① 讀與讀不阻塞。
② 讀與寫互相阻塞。
③ 寫與寫互相阻塞。

讀就是共享鎖,寫就是獨佔鎖。Select就是讀,DML就是寫。上面的規則,其實就是,當兩個線程同時操作同一頁時:Select與DML互相阻塞、DML互相阻塞、Select互不阻塞。這種規則很容易理解。所有的數據庫也都是這樣,但是,Oracle不一樣。

Oracle的規則是:
① 讀與讀不阻塞。
② 讀不阻塞寫。
③ 寫阻塞讀。
④ 寫與寫互相阻塞。
看出來沒,Oracle與MySQL的區別:“讀不阻塞寫”。

Oracle比MySQL向前走了一步,實現了讀不阻塞寫的Buffer Pin Lock。在讀多寫少的環境,讀不阻塞寫,可以有效的減少競爭。不像CBC Latch數量的問題,有一個“innodb_page_hash_locks”參數可以簡單的彌補MySQL在此點上的不足。Buffer Pin Lock的相容、阻塞規則,是無法被改變的。只能期待後續MySQL代碼的進步。

第四節
MySQL的棋高一着:共享的CBC Latch

當然,也不是所有地方Oracle都比MySQL棋高一着。有一個地方,MySQL還是有優勢的。MySQL的CBC Latch只保護搜索HASH表:

如上圖紅色虛線框中的畫,假設Bucker 2後的Node2是目標,在找到Node2後,MySQL將馬上釋放CBC Latch。Oracle不一樣,如下圖:

Oracle中在找到目標的Node 2後,並不馬上釋放CBC Latch,還要再修改Buffer Pin Lock的鎖變量、設置Buffer Pin Lock鎖,然後才能釋放CBC Latch。(注:Oracle的Buffer Pin Lock鎖就在圖中的Node 2的內存中)

有看官說,“不就是多了個加個鎖的操作嗎,速度是很快的,Oracle的CBC Latch持有時間也不會比MySQL的長多少啊!”

話不能這麼說,加鎖雖然是耗時很短的操作,特別是Buffer Pin Lock這種“低級內存級鎖”,也就是修改個內存標誌位的事。但是,正是因爲“修改”二字,導致Oracle的CBC Latch變成獨佔的了。所以,在Oracle 10G之前,CBC Latch沒有模式,只有持有、不持有。因此只要持有CBC Latch,就是獨佔的,沒有共享CBC Latch這一說。

但Oracle當然也非浪得虛名,在11G後,對CBC Latch、Buffer Pin Lock機制做了很大的調整。最大的改變發生在11.2.0.4後,在這個版本中,共享模式的CBC Latch得到了更爲普及的使用。但總體上來說,獨佔模式的CBC Latch,還是比MySQL中要多。在這一點上,MySQL算是扳回一局。並不是所有地方,Oracle都比MySQL先進。Oracle的最低層內核,其實還有提升空間。

好了,爲了照顧看熱鬧的羣衆,我並沒有對MySQL、Oracle的部分原理做太過細緻的剖析,省略了調試過程的細節。對調試技術、數據庫源碼有興趣的朋友,請持續觀注美創新運維新數據公衆號,精彩持續中。

美創運維中心數據庫服務團隊擁有Oracle ACE 1人、Oracle OCM 10餘人、Oracle OCP 40餘人。著有《Oracle數據庫性能優化方法論和最佳實踐》、《Oracle DBA實戰攻略》等書籍。目前運維數據庫2000餘套,精通Oracle、DB2、MYSQL、MSSQL、達夢等各項數據庫技術,擁有豐富的處理實戰經驗。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章