MySQL資料庫自問世以來,就因它的體積小、速度快、低成本等優勢受到衆多企業的追捧。同時由于它的完全開源特性,更增進了廣大資料庫愛好者對其深入研究的興趣,通過源碼的研究與探索,MySQL越來越多的優秀特性被廣泛挖掘出來。
本文将圍繞MySQL table-cache相關參數進行相應的源碼解讀及性能分析,旨在為使用MySQL的衆多資料庫工程師提供一些實際開發或運維工作的助益。
二、參數源碼解讀table-cache相關參數具體包括:

open_files_limit;

max_connections;

table_open_cache;

table_definition_cache。
MySQL執行個體程序在啟動時會根據配置檔案my.cnf中對這四個參數的設定進行自适應的調整生效,由于MySQL在設定這四個參數時存在嚴格的順序和依賴關系,故将它們放在一起分析讨論。
首先看一下源碼中關于這四個參數的自适應關系函數(源碼位于MySQLd.cc),該函數在main函數中被調用,内部分别調用了各自的設定函數。
void adjust_related_options(ulong *requested_open_files)
{
…
adjust_open_files_limit(requested_open_files);
adjust_max_connections(*requested_open_files);
adjust_table_cache_size(*requested_open_files);
adjust_table_def_size();
}
由于這四個設定函數存在嚴格的順序調用關系,下文将依次對各個函數進行拆分說明。
1、open_files_limit該參數作用為限定了MySQL執行個體程序能打開的檔案描述符最大值。關于該參數設定函數的聲明:
void adjust_open_files_limit(ulong *requested_open_files)
函數中的參數requested_open_files為指針變量,這是一個貫穿前後的指針,它指向的記憶體中所存儲的内容将會被後續函數多次用到。
limit_1= 10 + max_connections + table_cache_size * 2;
limit_2= max_connections * 5;
limit_3= open_files_limit ? open_files_limit : 5000;
request_open_files= max<ulong>(max<ulong>(limit_1, limit_2), limit_3);
由于該參數首先被設定,是以這部分計算邏輯所取用的變量均來自配置檔案中的設定值(源碼中的table_cache_size對應配置檔案中的table_open_cache),根據計算後得出的limit_1,2,3将取最大值存放在變量request_open_files中。
PS:此處需注意的是,request_open_files和requested_open_files是不同的。
effective_open_files= my_set_max_open_files(request_open_files);
open_files_limit= effective_open_files;
随後,函數my_set_max_open_files會以request_open_files作為參數,來計算effective_open_files的值。此函數中的計算邏輯會根據不同的系統環境選擇不同分支,但因本文的分析和測試均針對伺服器,故以Linux分支為主。
不同的分支計算邏輯略有不同,在Linux環境下,函數中會調用系統函數getrlimit和setrlimit來擷取系統資源的最大使用量,在系統允許範圍内,計算所得到的effective_open_files的值與request_open_files的值相等。筆者所用測試實體機測得的系統允許上限值為1024*1024。
此外,另一個可能影響effective_open_files數值的是MySQL執行個體的啟動方式:

若MySQL執行個體在非root下啟動,則effective_open_files的值會受到系統對于單程序句柄的限制,即指令ulimit –n得到的值;

若在root下啟動執行個體,則不會受到系統對單程序的限制。
計算後所得的effective_open_files的值會賦給open_files_limit,作為執行個體中最終生效的實際參數。
概括來講,在未達到最大值時,計算所得的effective_open_files 與request_open_files 值相等,并将effective_open_files的最終值賦給open_files_limit;若超過最大值則effective_open_files會以配置檔案中的open_files_limit設定值生效,若配置檔案中設定也超限,則取系統對單程序檔案描述符的限制做生效值。為了更清晰的說明此處邏輯,筆者進行了以下對比測試。
為了便于更直覺的看清測試結果,我們更改系統單程序檔案描述符限制為56789,更改系統對全局檔案描述符限制為655350。然後分别使用下列三種配置檔案啟動執行個體:
配置一:配置檔案中設定open_files_limit值為1040000。
使用非root使用者啟動MySQL執行個體,進入資料庫檢視變量生效值:
此時是以系統對單程序檔案描述符限制生效的。
關閉執行個體程序,使用root使用者啟動MySQL執行個體,再次進入資料庫檢視變量生效值:
此時則以配置檔案中的設定值生效。
配置二:配置檔案中設定table_open_cache=520000,open_files_limit=655350。
在root使用者下啟動執行個體,可以看到此條件下可以按照配置檔案生效:
随後,更改配置檔案中設定table_open_cache=530000,open_files_limit不變,在root使用者下重新開機執行個體,觀察變量生效情況:
産生變化的原因是根據修改的table_open_cache值計算所得的effective_open_files會超出系統允許的上限,故MySQL會使用配置檔案中的設定生效,并重新計算table_open_cache的值。
配置三:配置檔案設定table_open_cache=530000,open_files_limit=1049000。
在root使用者下重新開機執行個體,觀察變量生效情況:
此時由于計算所得的effective_open_files和配置檔案中的open_files_limit設定值均超限,故使用系統對單程序檔案描述符限制作為生效值,并進一步計算得到實際的table_open_cache。
讓我們回到源碼中繼續解讀。在設定了open_files_limit生效值後,MySQL源碼中通過封裝C語言的标準輸出函數實作了自己的輸出函數,并在一定條件下向error.log中輸入相應資訊。
if (effective_open_files < request_open_files)
if (open_files_limit == 0)
sql_print_warning("Changed limits: max_open_files: %lu (requested %lu)", effective_open_files, request_open_files);
else
sql_print_warning("Could not increase number of max_open_files to "
"more than %lu (request: %lu)", effective_open_files, request_open_files);
根據源碼邏輯,當effective_open_files小于request_open_files的值時,error.log中就會記錄相應資訊。同時又根據配置檔案中是否設定了open_files_limit的值來輸出不同内容的錯誤資訊。以前文配置一為例,非root使用者啟動MySQL計算所得的effective_open_files小于request_open_files,檢視error.log中的資訊有如下内容:
對于root使用者啟動,由于比較條件不滿足,則無相應資訊輸出。
if (requested_open_files)
*requested_open_files= min<ulong>(effective_open_files, request_open_files);
函數體的最後在effective_open_files和request_open_files中取小值放在了指針requested_open_files所指的記憶體中,以便于後續函數對該變量的調用。
2、max_connections該參數限制了MySQL執行個體允許的最大連接配接數,在資料庫的維護工作中相較于其他參數也更容易直覺的接觸到,下面讓我們看一下在源碼中是如何對這個參數進行設定及生效的。
void adjust_max_connections(ulong requested_open_files)
此函數中的參數requested_open_files變量值即為前文函數中requested_open_files指針變量所指内容。關于max_connections的計算邏輯則相對簡單。
limit= requested_open_files - 10 - TABLE_OPEN_CACHE_MIN * 2;
if (limit < max_connections)
sql_print_warning("Changed limits: max_connections: %lu (requested %lu)",limit, max_connections);
max_connections= limit;
首先根據算式計算limit變量的值,此處用到的TABLE_OPEN_CACHE_MIN是在頭檔案sql_const.h中定義的宏定義變量,值為400。
随後将limit的值與配置檔案中設定的max_connections進行比較,若limit較小,則向error.log中輸出一串提示資訊,并以limit作為最終生效值;若limit較大,則直接以配置檔案的設定值生效,同時不列印提示資訊。以下為操作執行個體:
設定配置檔案中max_connections值為5000,其他配置沿用前文。以非root使用者啟動MySQL執行個體,在資料庫中檢視max_connections的值:
此時max_connections按照配置檔案中的設定進行生效,原因在于非root使用者啟動執行個體,requested_open_files取系統限定的effective_open_files值為56789,計算limit=56789-10-400*2=55979,該值遠大于5000,故取較小值即配置檔案中的設定值生效。
保持同樣的條件不變,僅更改配置檔案中的max_connections設定值為60000大于55979,使用非root使用者重新開機MySQL執行個體,再次檢視max_connections生效值,已經更新為預期的55979了。
3、table_open_cache此參數規定了記憶體中允許打開表的數量,從它的作用可以看出,當MySQL在處理查詢請求時,table_open_cache将會起到較大作用,有效設定并使用此參數可以降低熱點表的頻繁開關動作,進而改善性能。關于該參數設定的源碼如下:
void adjust_table_cache_size(ulong requested_open_files)
limit= max<ulong>((requested_open_files - 10 - max_connections) / 2,
TABLE_OPEN_CACHE_MIN);
if (limit < table_cache_size)
sql_print_warning("Changed limits: table_open_cache: %lu (requested %lu)",limit, table_cache_size);
table_cache_size= limit;
對比max_connections的實作函數可以看出,兩個函數的内部結構相似,都是以requested_open_files為參數,根據各自的邏輯計算出limit變量的值,并将其與配置檔案中的設定值比較,取更小的值作為最終生效值。需要注意的是,table_open_cache在源碼檔案sys_vars.cc中被限定了取值範圍(0,512*1024),這個範圍會對table_open_cache的實際生效值産生影響。下面看一些配置執行個體:
設定配置檔案中table_open_cache=30000,max_connections=5000,在非root使用者下啟動執行個體。
此模式下,requested_open_files=56789,max_connections生效值5000,算式(requested_open_files - 10 - max_connections) / 2值為25889>400,故limit=25889,同時小于配置檔案中的設定值,故最終生效值應為25889。在資料庫中檢視變量的實際生效值:
結果符合預期。
保持其他條件不變,更改配置檔案中table_open_cache值為20000,再用非root使用者重新開機執行個體,此時limit值仍為25889,但已大于配置檔案中的設定值,故生效值應為20000。到資料庫中檢視實際生效值驗證。
此參數乍一看很容易與table_open_cache相混淆,畢竟這對“孿生兄弟”從生效到功能都有相近之處。table_definition_cache定義了記憶體中可打開的表結構數量。此參數的設定函數與前列很大的一個不同點是沒有使用requested_open_files作為參數,僅僅聲明了一個無參函數:
void adjust_table_def_size()
此參數的設定算式僅使用了已生效的table_open_cache作為計算變量。
default_value= min<ulong> (400 + table_cache_size / 2, 2000);
table_open_cache函數中有一個很值得關注的點,在函數體内涉及了MySQL動态哈希連結清單的通路。這個連結清單是MySQL在啟動主函數中定義并且用來存放部分系統變量的動态連結清單,下面我們來詳細了解一下函數内部的通路流程。
首先函數體内定義了一個系統變量類型的指針var,随後調用函數通路哈希表,并将定位到的記憶體塊指派給類對象var。
var= intern_find_sys_var(STRING_WITH_LEN("table_definition_cache"));
此處STRING_WITH_LEN()是一個宏定義,傳回内容為對應字元串本身及長度。我們到intern函數内部看一下:
var=(sys_var*)my_hash_search(&system_variable_hash,(uchar*) str, length ? length : strlen(str));
return var;
intern函數内部調用my_hash_search函數查找table_definition_cache字元串在哈希連結清單中對應的結點,并将找到的位置傳回類對象指針var。在找到var指針的目标結點後,table_definition_cache設定主函數就把前面計算所得到的值寫入var所指向的記憶體。
var->update_default(default_value);
值得注意的是,即便函數已将計算結果賦給目标結點,但函數依然會首先判斷配置檔案中是否設定了table_definition_cache的值。若配置檔案中未設定,則以計算所得的結果作為生效值;若配置檔案中有對應設定,則優先以配置檔案中的設定生效。
if (! table_definition_cache_specified)
table_def_size= default_value;
另一個需要注意的是雖然MySQL預設配置檔案中設定的table_definition_cache優先生效,但在頭檔案sql_const.h中宏定義了table_definition_cache的下限值為400,故即便在配置檔案中設定了一個很小的值,MySQL也會自動将生效值上調為下限值。下面看一些對應的配置執行個體:
我們選擇前例中requested_open_files=56789,max_connections=5000,table_open_cache=25889的條件進行測試。
首先,我們在配置檔案中設定table_definition_cache=35486并将其注釋,儲存檔案。
重新開機MySQL執行個體,令修改的配置檔案生效。根據前面對源碼的分析,計算公式default_value= min<ulong> (400 + table_cache_size / 2, 2000)=2000,此時配置檔案中的設定無效,應以計算結果生效。進入資料庫中檢視生效值:
然後修改配置檔案,取消table_definition_cache的相關注釋,儲存配置檔案并重新開機執行個體,到資料庫中檢視生效值:
此時已按照配置檔案設定生效。
最後為驗證MySQL對table_definition_cache下限的自适應調整,我們修改配置檔案中的對應值為table_definition_cache=15并儲存,重新開機,再次進入資料庫檢視生效值:
可以看到,生效值已被MySQL自适應調整為源碼中宏定義的下限值。
至此,上文已完成對table_cache相關的源碼分析内容,當我們在實際工作中需要調整相關參數,可以參考前文并配置。現在讓我們進一步思考,在知曉了這些參數間的關聯和配置方法後,如何設定相應的值才算合理?這些參數對MySQL的實際使用性能又會有多大的影響?下文将會對這部分内容進行測試分析。
三、 參數性能影響及測試分析上文介紹了table-cache相關參數在MySQL資料庫正常運轉過程中存在至關重要的作用,但并不是每個參數的微調都會對性能産生顯著的影響,以下将對它們逐一進行說明。
嚴格來講,open_files_limit和max_connections對MySQL的重要性并非展現在性能方面。
對于open_files_limit來說,不合理的設定将會直接導緻MySQL執行個體down掉或在啟動時根本無法正常拉起程序。對于這些場景,MySQL會有簡單直接的錯誤資訊來提示DBA需要進行相應的調整。
對于max_connections來說,設定過大可能會對其它參數的生效産生影響,設定過小又無法滿足業務高峰時的連接配接需求,進而造成大量的連接配接等待和逾時。通常根據實際情況設定在能夠滿足業務峰值的大小即可。
基于上述原因,這兩個參數的性能影響在此不做深入探讨,而把重點放在對另外兩個參數的測試及分析。
1、table_definition_cache(TDC)本節對TDC可能産生的性能影響進行測試分析。使用的MySQL測試版本為5.7.18,測試伺服器為單執行個體單庫,庫中共建立40000張表,每張表内5000行資料。測試條件為保證其他參數一緻的前提下分别設定TDC=10000和TDC=50000進行對比。
首先比較兩種場景下全局變量的差別,分别修改配置檔案中TDC設定值為10000和50000,再分别重新開機執行個體,檢視相關全局變量如下:

TDC=10000

TDC=50000
通過對比可以看出,重新開機後兩種場景的TDC生效值分别已達到預期的10000和50000。同時,我們在此處對比另外一個變量open_table_definitions,這個變量表示在目前狀态下打開的表結構數量,兩種場景下在MySQL執行個體剛剛重新開機後由于TDC的不同,打開的表結構數量是不同的:

TDC=10000的場景下打開的表結構數量被限制在10000;

TDC=50000的場景下執行個體中的表結構全部被打開(其中有40000張測試表+219張系統表)。
對比結果說明當TDC生效值大于庫中全表數量時,執行個體啟動時會将所有表結構加載到記憶體;當TDC生效值小于庫中全表數量時,MySQL會按照TDC的生效值加載相應數量的表結構到記憶體。
随後我們來比較TDC的差别是否會對性能産生影響。測試使用sysbench工具模拟用戶端向伺服器發送請求,申請通路的表數量為35000,連接配接并發數為50,連接配接發送請求持續時間為5min,使用以上測試模式分别測試TDC=10000和TDC=50000兩條件下四種基本SQL語句的qps,對比結果如下:
從測試結果對比可以看出,不同的TDC對MySQL基準性能的影響并不大。那麼導緻這樣的因素是什麼?
我們檢視兩個測試後全局狀态變量open_table_definitions的值進行對比:


通過比較可以發現,雖然TDC設定值為10000的模式無法在啟動執行個體時将所有表結構都加載入記憶體,但在實際請求到達伺服器時,MySQL允許在記憶體中“超載”TDC定義的表結構數量,這就使得執行個體目前已打開的表結構數量可超過TDC的限定值。同時,對于單一表來說,重複通路并不會增加表結構的打開次數。是以,TDC對于性能的影響便不是很大了。
2、table_open_cache(TOC)本節測試TOC可能産生的性能影響。使用的MySQL版本及測試庫環境與TDC測試相同。首先比較兩種不同TOC對性能帶來的影響。根據前文,使用root和非root使用者啟動執行個體會導緻open_files_limit的生效值有差別,進而影響TOC的生效值。本例在配置檔案中設定TOC值為350000,再分别使用root及非root使用者啟動執行個體,觀察實際生效值如下:

root使用者啟動:

非root使用者啟動:
由于TOC決定了記憶體中打開表的數量,功能上對查詢SQL的影響更為明顯,故使用sysbench僅發送select語句并統計qps,申請通路的表數量是35000,通路持續時間為5min。更改不同的連接配接并發數分别測試qps并繪制成折線圖,結果如下:
從曲線圖可以看出,TOC較大(=350000)的條件下,查詢語句的qps随并發數增加變化比較明顯,整體呈現先迅速上升後平穩回落的趨勢,在64-128并發範圍内達到高峰。反觀TOC較小(=30262)的模式下qps随并發數的增加無明顯變化,而且持續處于較低水準。
為了更加清晰的分析TOC對性能的影響,我們統計了每個并發下獨立測試的緩存命中率并繪制成曲線進行比較:
比較二者命中率曲線可以看出,TOC較大的模式下,緩存命中率随并發數的變化趨勢與qps曲線基本一緻,雖然存在一定的波動,但整體命中率均在99%以上,這時我們可以判定緩存的效率是較高的;但TOC較小的模式下,緩存命中率在不同并發數下普遍較差(低于50%),同時随并發數的增加還有急速下降的趨勢。
我們再統計一下各個并發測試下的table_open_cache_overflows的值繪制成曲線并觀察:
從曲線圖可以看出,TOC較大的模式下overflow值非常小(基本為0),這與該模式下緩存命中率較高的結果相吻合;但TOC較小的模式下則存在較大的overflow,這說明該模式下較小的緩存無法滿足大量的并發通路請求,緩存不得不持續将新表刷入記憶體。
四、總結通過本文對table-cache相關源碼的分析及對比測試,我們可以得出一些相關結論:

open_files_limit,max_connections, table_open_cache, table_definition_cache四個參數在MySQL執行個體啟動時依次生效,且互相存在制約關系,若需要單獨更改某一變量,要注意可能産生的影響;

open_files_limit參數的生效會受到不同啟動方式的影響進而影響其它參數的生效值,設定時要按需選擇;

open_files_limit和max_connections參數對性能影響較小,設定時可滿足業務量需求即可,二者更多影響的是MySQL執行個體的正常運作;

table_definition_cache參數對性能影響較小;

table_open_cache參數對實際性能影響較大,但生效值需要在上下限間合理設定,為盡可能發揮cache性能,生效值應設定為大并發下可維持較高命中率的同時不發生緩存overflow。
以上是本文全部内容,希望讀到此文的廣大前輩和同行能夠積極提出文中的不足并不吝指正。
原文釋出時間為:2018-06-22
本文作者:曹嘯
本文來自雲栖社群合作夥伴“
DBAplus社群”,了解相關資訊可以關注“
”。