轉載 https://www.zhihu.com/question/19866767/answer/14942009mysql
http://whuai.blog.51cto.com/3539000/862197算法
在MYSQL中,一樣有不少類型的系統對象,包括表、視圖、存儲過程、存儲函數等,但因爲MYSQL的插件式存儲引擎及其它實現方面的特色,其每一種對象的緩存方式都不一樣,或者說這些對象的緩存不是經過一種統一的方式來管理的,每一種對象的緩存都是有本身的特色,而且緩存的內容也有很大的差別,下面先只敘述一下表對象的緩存方式。sql
表字典對象緩存,顧名思義,是將某個表對象的字典信息(定義內容)緩存到內存中,用來提升對錶訪問的效率。在某一個表第一次被訪問過以後,在服務器沒有關閉而且表定義沒有被修改的條件下,訪問這個表時,只須要從內存中找到這個已經緩存起來的對象而且作相應操做便可,而不須要再次從系統表中讀取它的定義而且解析,而後再作相應的操做。數據庫
當某一個用戶要查詢某一個表的數據時,系統首先會找到這個表。上面已經提到過,由於MYSQL實現了表的緩存,因此首先會從緩存中尋找這個表,表字典對象的緩存是經過HASH表來管理的,MYSQL系統中,專門有一個HASH表(源代碼中的名字是table_def_cache)用來存儲組織表對象。緩存
因此首先經過表的名字(包括了模式名)來構造一個HASH鍵值(KEY),用來從HASH表中搜索對象。安全
可是對於表對象的緩存,不僅是簡單的將一些表的定義經過HASH存儲起來就算完了,那這樣的話緩存可能沒有任何意義,或者說侷限性很是大,這樣可能致使一個用戶在表對象上作了什麼標誌或者修改等都會影響到其它用戶,這種影響是不可預期的,更重要的緣由是,MYSQL是插件式的數據庫,每個用戶獲得表對象以後還須要將表實例化,這個實例化的對象只有本身才能使用,因此不是簡單的全部用戶都使用同一個緩存對象便可完成的。它的緩存實際上是用了一種能夠稱爲「共享私有化緩存」,看上去這個說法是矛盾的,其實並非這樣的,它在緩存過程當中用到一個叫TABLE_SHARE的結構體,一個這個結構體惟一對應MYSQL中的一個表對象,這裏是不區分任何存儲引擎的,它實際上就是對具體一個表的定義的翻譯或映射,也就是說當須要打開一個表的時候,這個表首先是在MYSQL的系統表中存儲的(固然系統表是分不一樣的存儲引擎的,不一樣的存儲引擎有本身的系統表,這裏所說的MYSQL的系統表應該是一種統稱,實際上是具體某一個存儲引擎的系統表),若是要使用了,首先須要從系統表中將這個表的全部信息都讀入到內存中來,這些信息包括表名、模式名、全部的列信息、列的默認值、表的字符集、對應的frm文件的路徑、所屬的存儲引引擎(MYSQL中的表能夠單獨定義本身的存儲引擎)、主鍵等等,固然還有不少其它信息,全部這些信息讀入內存中的時候首先就是經過結構體TABLE_SHARE來存儲的,至關於這個結構體是一個表對象緩存的第一層,同時從名字就能夠看出,這個結構體是因此用戶均可以共享的一個表對象,因此它是靜態的,不容許修改的(內存中),從系統表中讀取進來以後直到這個表從緩存中刪除,中間不會作任何的修改。服務器
那麼用戶要訪問一個表,只是構造了TABLE_SHARE是遠遠不夠的,並且這個結構體對象也不是直接給用戶使用的對象,在構造了這個結構體以後,首先須要將其緩存起來,由於這個結構體就是咱們這裏討論的核心,它就是咱們要緩存的對象,因此首先須要根據上面計算獲得的KEY將這個表對象緩存到table_def_cache中,這個緩存操做到這裏就結束了。架構
可是若是這個問以前已經被訪問過了,那麼就不須要再像上面同樣構造這個共享結構體了,而是直接經過HASH的KEY值在table_def_cache中找到這個共享結構體便可。併發
從上面的敘述中知道,當系統獲得一個SHARE對象以後,系統會真正的構造一個新的對象交給當前的操做,這個對象上面已經說過了,確定不是TABLE_SHARE,由於這個是緩存對象,它是靜態的,只讀的,真正與用戶交互的是TABLE_SHARE的一個衍生品,它對應的結構體名字爲TABLE,它是真正的在操做中被使用的對象,那麼是如何從TABLE_SHARE變爲TABLE的呢?oracle
其實這兩個結構體的不少成員是相同的,而且能夠直接複製過去,上面已經說了,TABLE_SHARE是一個靜態的緩存對象,那麼相對而言,TABLE就能夠稱做是一個相對動態的、一個正在進行一些操做的實例了,TABLE中有一個成員就是直接指向了TABLE_SHARE的;還有一些成員好比record,這個是用來構造插入操做中的一條記錄的,這個系統會根據這個表定義的每個列及其數據類型等提早構造好;field用來存儲全部這個表中的列信息的,這個信息實際上是徹底將SHARE中的信息克隆過來的。其它的一些小的細節就不敘述了,不過還有兩個很重要的東西必需要說一下。
由於上面已經提到了,TABLE這個對象是一個動態的,被實例化的對象,它至關因而一個被打開的表,它已經不是在MYSQL的上層了,而是具體到了某一個存儲引擎了,因此這裏還須要對這個對象構造它的有關存儲引擎的信息而且打開這個表。
由於MYSQL是一個插件式的數據庫管理系統,對於表對象的管理,MYSQL層與存儲引擎層就是在這裏分開的,TABLE算是它們之間的橋樑,下層是存儲引擎,上層就是MYSQL了,對於一個MYSQL的存儲引擎,都要提供一些公共的接口來驅動其存儲引擎,這些接口包括:close_connection、savepoint_set、savepoint_rollback、savepoint_release、commit、rollback、create(建立句柄)、ha_create (建立一個表)、ha_open(打開表)、ha_close(關閉表)、ha_write_row(插入一條記錄)、ha_delete_row(刪除一條記錄)、ha_reset(將實例恢復到新打開的狀態)等操做,這些接口都是上層調用來操做對應的存儲引擎的,也能夠被稱做是MYSQL與存儲引擎之間交流的通道。
那麼從SHARE到TABLE之間的過渡或者叫作SHARE的實例化過程,首先就須要調用函數create來建立一個對應的存儲引擎句柄,建立以後就經過函數ha_open來打開這個表,打開表主要是對這個新建立的存儲引擎句柄進行一些初始化操做。在打開以後,這個表的實例化也就算完成了,而這個已經被打開的實例句柄就掛在TABLE結構體中,從這裏也能夠看出,TABLE是與一個操做對應的實例化的對象,它只能在同一時間內被一個操做所使用。
在被實例化以後,這個就能夠直接與存儲引擎進行交互了,好比插入一條記錄,能夠直接調用TABLE下面已經被實例化的存儲引擎句柄下的函數ha_write_row便可。
當一個操做完成以後,它所實例化的表就不須要了,此時系統不是將這個本地的實例直接釋放掉,而是將其保存下來了,保存下來是爲了下次某一個用戶再次訪問這個表的時候不須要再次進行實例化了,直接拿過來用便可,固然可能須要一些額外的操做,好比將實例狀態恢復,調用函數ha_reset便可。
系統保存實例是直接將其放在SHARE的一個free_tables鏈表中,但首先要從used_tables鏈表上摘下來,這兩個鏈表都是用來保存這個表的全部實例的,used_tables用來存儲正在使用的實例,free_tables用來存儲全部當前未使用的實例,有可能在併發比較高的狀況下,可能在used_tables中有多個,但free_tables中沒有,都執行完成以後則相反,那麼若是此時再有用戶又操做這個表,系統能夠直接從free_tables找一個拿來用便可。
如今能夠知道,在MYSQL中,表對象的緩存實際上是用兩個部分,一部分是SHARE的緩存,也就是說多個不一樣的表的SHARE對象的緩存;另外一部分就是每個SHARE結構被實例化以後的實例對象的緩存,MYSQL用來管理緩存空間大小的方法是經過計數來實現的,默認狀況下,系統中總的SHARE個數不能超過400個,全部SHARE的全部表實例的個數也不能超過400個。
上面提到的都是關於表對象SHARE結構的緩存,既然是緩存,確定相應的有它被刪除或者淘汰的問題,固然在這裏也不例外。那麼在什麼狀況下SHARE結構會被淘汰或者刪除呢?很明顯,若是隻是對這個表進行增刪改等沒有涉及到修改表定義的操做,SHARE是不會被刪除的,只有可能會被淘汰,由於若是查詢太多表的話,表對象緩存個數是有限制的,當到達這個數目以後,系統會自動將一些不常用的SHARE淘汰掉,這個很容易理解。
那麼通常狀況下,只有對錶結構、依賴關係、表定義等方面進行修改以後,由於這個表的版本被更新了,若是繼續將其緩存的話是不安全的,或者是錯誤的,或者致使一些不可預知的問題,因此這種狀況下這個表對象的緩存SHARE對象必需要從緩存中刪除,同時要刪除它上面因此被實例化的表對象緩存結構,這樣當其它用戶在等待表對象的修改操做完成以後(由於修改過程當中這個表是被上了鎖的,進行操做須要等待),又一次像上面敘述的同樣,首先是從緩存中找這個表的緩存對象,若是找不到的話再從數據字典(系統表)中讀取進來,而後繼續操做便可。
到這裏關於表的緩存及一些其它的內容就敘述完了。
總結:
1)上面提到的表的緩存機制有很大的好處的,由於它不是全字典緩存(全字典緩存的意思就是在數據庫啓動時將全部的數據字典信息都一次性載入內存中來,這樣在使用過程當中就效率很是高,但在DDL操做方面有很大的不足),它是用到的時候再載入,修改以後直接刪除有可能再從新載入,這樣的實現方式減小了DDL操做或DDL的回滾致使的字典緩存維護工做的代價。
2)有效的利用了內存空間,由於能夠經過設置表對象緩存空間的大小來控制內存的使用狀況,同時只有用到的對象纔會被載入到內存中,提升了內存的利用率。
3)上面所敘述的MYSQL表緩存實現方案雖說是比較先進的,可是在效率方面仍是有些優化的空間的,好比上面提到的,在用來控制緩存空間大小是根據實例的個數來計算的,在系統中默認最大值是400個,若是超過這個值系統會自動淘汰一些不經常使用的實例,可是若是一個表的定義很是大,那麼併發狀況下,有可能會創建不少個實例,假設接近400個,那麼這樣算下來有可能會將操做系統的內存用光,這個是不可控制的,也是不可預期的。對於SHARE的緩存也是同樣,若是一個用戶訪問了不少不一樣的定義很大的表,也會有一樣的問題。
4)從上面也能夠看出,爲了實現插件式的數據庫,其實仍是有一些效率的代價的,在表的緩存方面,中間加入了一層SHARE的緩存,真正用到的時候還須要實例化,由於每個用戶的操做及不一樣時間的狀態都是不一樣的,因此每個用戶必需要再在SHARE的基礎上實例化一個新的對象出來,這樣就給內存、CPU帶來了必定程序上的浪費及壓力。
問題的解決:
1)SHARE緩存:我我的認爲有一個更好的辦法來很精確經過具體的空間大小來管理表緩存空間,由於對於SHARE而言,它是靜態的,它是個結構體,經過使用計數來控制內存的使用,有可能會形成內存用光的狀況,那麼對於SHARE對象,徹底能夠把它流式化(扁平化),也就是說等把這個結構體的大小計算出來,申請相應的空間,將結構體中的全部信息都按照固定的順序寫入到這塊內存中,那麼這樣一個SHARE所佔的空間大小就固定了,這樣能夠徹底經過設置空間大小來管理表對象緩存空間了,這樣上面提到的內存用光的問題就天然解決了,固然這個大小須要根據計算機的內存大小合理的設置,至少不會出現不可預料的問題。
2)TABLE緩存:TABLE實例的緩存一樣存在上面的問題,解決方案與上面的思想差很少,由於這個對象是一直被用的,它是一個實例,因此就不能直接像上面同樣,將其流式化,而是能夠經過申請一片鏈接的空間,這個實例中全部的指針或者其成員的值都指向(有可能要對齊)這個空間中的指定位置,這樣這個結構體的使用沒有任何改變,但其佔用的空間大小是固定的,一樣能夠經過用戶手動設置TABLE實例緩存空間的大小來管理緩存空間,這樣也避免了表定義太大致使內存用光的問題。
本文出自 「懷瑾握瑜」 博客,請務必保留此出處http://whuai.blog.51cto.com/3539000/862197