壹、背景
近期,公司RDS雲產品的MySQL Server版本進行升級,由目前使用的5.7.26版本升級到最新版本5.7.31;升級後測試同學發現:在MySQL創建用戶後,5.7.31版本重新啟動集群會出現啟動失敗的現象;而5.7.26版本在相同測試場景下是正常啟動的。這到底是為什麽呢?
二、問題復現
2.1 實驗環境
2.2 操作步驟
按照測試同學的測試步驟,首先創建壹個用戶:
然後關閉mysqld;這裏需要介紹壹下,我們集群的關閉方式是如下方式:
這種方式的內部實現類似於kill -9模式。所以我在線下環境使用kill -9的方式來復現,操作如下:
然後重啟mysqld,操作如下:
此時問題復現了,mysqld啟動失敗,我們查看了下error日誌,信息如下:
根據報錯信息可以看出:MySQL的權限系統表發生了損壞,導致了mysqld啟動失敗;由於在MySQL 5.7及其之前版本該表是MyISAM引擎,且該引擎不支持事務,所以在mysqld異常崩潰會導致該類型引擎表的損壞;但在mysqld啟動時是有參數控制MyISAM引擎的恢復模式,且該參數在我們產品中也配置到了my.cnf中,如下所示:
2.3 參數解析
對於該參數的官方文檔的解釋如下:
設置MyISAM存儲引擎恢復模式。選項值是OFF、DEFAULT、BACKUP、FORCE或QUICK的值的任意組合。如果指定多個值,請用逗號分隔。指定不帶參數的選項與指定DEFAULT相同,指定顯式值" "將禁用恢復(與OFF值相同)。如果啟用了恢復,則mysqld每次打開MyISAM表時,都會檢查該表是否標記為已崩潰或未正確關閉。(只有在禁用外部鎖定的情況下運行,最後壹個選項才起作用。)在這種情況下,mysqld在表上運行檢查。如果表已損壞,mysqld將嘗試對其進行修復。
服務器自動修復表之前,它將有關修復的註釋寫到錯誤日誌中。如果您希望能夠在無需用戶幹預的情況下從大多數問題中恢復,則應使用選項BACKUP,FORCE。即使某些行將被刪除,這也會強制修復表,但是它將舊的數據文件保留為備份,以便您以後可以檢查發生了什麽。
全局變量,只讀變量,默認為OFF。
三、問題修復
這類MySQL用戶表損耗的問題解決方式也是有多種,我這裏列舉其中壹種:
(1)my.cnf中的[mysqld]標簽下添加skip_grant_tables,啟動時跳過加載系統字典。
(2)重啟mysqld,然後修復mysql schema下的所有表。
(3)在[mysqld]標簽下註釋或刪除掉skip_grant_tables,然後重啟mysqld。
此時mysqld是可以正常啟動的,無異常。
四、深入排查
在產品化中,以上修復方式很不優雅,只是作為臨時的解決方案;並且也存在壹些令人疑惑的點:
帶著這些疑問,我們繼續排查出現該現象的原因;此時Google也沒有找到壹些有效的信息,那麽只能通過MySQL源代碼來尋找壹些答案。
首先需要下載mysql 5.7.31版本的源代碼,並搭建mysql debug環境;具體步驟可以自動Google搜索壹下,本文就不再贅述了。
在源代碼中搜索壹下關鍵詞,用於打斷點的位置,然後進行調試:
定位到相關代碼,大概是sql/mysqld.cc的4958行,且存在if條件判斷,此時我們開始調試:
通過以上調試信息,可以判斷出acl_init函數返回的值為真;此時我們查看該函數的代碼 (sql/auth/sql_auth_cache.cc:1365):
根據該函數的註釋發現:該函數是初始化負責用戶/數據庫級特權檢查的結構,並從mysql schema中的表中為其加載特權信息;且return值為1代表的是初始化權限失敗。
此後開始逐步調試,觀察return相關信息,當調試到lock_table_names函數時,我們發現在Phase 3時return值為true,且根據代碼註釋發現true代表是Failure;具體代碼如下(sql/sql_base.cc:5549):
調試信息如下:
可以看到flags的值為0,而MYSQL_OPEN_SKIP_SCOPED_MDL_LOCK為宏定義值0x1000,與flags的值 做按位與操作,結果自然也是0,當然MYSQL_LOCK_IGNORE_GLOBAL_READ_ONLY也是如此;need_global_read_lock_protection是bool類型值,代表是否需要全局讀鎖的保護,這個值是在table- >mdl_request.type不為MDL_SHARED_READ_ONLY發生改變;check_readonly函數相關信息 下面概述。
此時也查看了下MySQL 5.7.26版本代碼作為對比,發現lock_table_names函數下的Phase 3後的部分代 碼是在5.7.29版本後新增的。如果是git clone的MySQL代碼可以用git blame命令查詢文件變化的信息:
上述展示的信息中,最左側的列值為commit id為05824063和0405ebee,有興趣的同學可以詳細看下。
此功能解決的問題是 BUG#28438114: SET READ_ONLY=1 SOMETIMES DOESN'T BLOCK CONCURRENT DDL.;當然這個代碼的變更功能也在5.7 Release Notes中有所體現,如下所示( f中是否包含read_only相關參數,檢查之後發現確實是使用了該參數, 如下:
此時註釋掉該參數,然後再次啟動mysqld,發現MyISAM表可以自動修復,且正常啟動;error log信息如下:
由於docker壹些限制,我們在mysqld啟動會涉及兩次;所以解決該問題的方式為:第壹次mysqld的啟動時先關閉read_only參數,第二次啟動時開啟read_only參數。之所以選擇默認開啟read_only參數, 是為了避免在mysqld啟動後,選主邏輯未完成時的保護措施;當然選主完成後,會自動對master執行 set global read_only=0 操作。
五、總結
六、附錄
調試的棧幀信息如下,有興趣的小夥伴可以研究下:
熟悉MySQL體系結構和innodb存儲引擎工作原理;以及MySQL備份恢復、復制、數據遷移等技術;專註於MySQL、MariaDB開源數據庫,喜好開源技術。
原文鏈接:/articles