亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

MySQL 8.0數(shù)據(jù)字典緩存管理機(jī)制解析

 更新時(shí)間:2024年07月16日 09:46:10   作者:華為云開(kāi)發(fā)者聯(lián)盟  
MySQL 8.0中的數(shù)據(jù)字典,通過(guò)對(duì)兩級(jí)緩存的逐級(jí)訪問(wèn),以及精妙的對(duì)緩存未命中情況的處理方式,有效的加速了在不同場(chǎng)景下數(shù)據(jù)庫(kù)對(duì)DD的訪問(wèn)速度,顯著的提升了數(shù)據(jù)庫(kù)訪問(wèn)元數(shù)據(jù)信息的效率,這篇文章主要介紹了解讀MySQL 8.0數(shù)據(jù)字典緩存管理機(jī)制,需要的朋友可以參考下

背景介紹

MySQL的數(shù)據(jù)字典(Data Dictionary,簡(jiǎn)稱DD),用于存儲(chǔ)數(shù)據(jù)庫(kù)的元數(shù)據(jù)信息,它在8.0版本中被重新設(shè)計(jì)和實(shí)現(xiàn),通過(guò)將所有DD數(shù)據(jù)唯一地持久化到InnoDB存儲(chǔ)引擎的DD tables,實(shí)現(xiàn)了DD的統(tǒng)一管理。為了避免每次訪問(wèn)DD都去存儲(chǔ)中讀取數(shù)據(jù),使DD內(nèi)存對(duì)象能夠復(fù)用,DD實(shí)現(xiàn)了兩級(jí)緩存的架構(gòu),這樣在每個(gè)線程使用DD client訪問(wèn)DD時(shí)可以通過(guò)兩級(jí)緩存來(lái)加速對(duì)DD的內(nèi)存訪問(wèn)。

整體架構(gòu)

圖1 數(shù)據(jù)字典緩存架構(gòu)圖

需要訪問(wèn)DD的數(shù)據(jù)庫(kù)工作線程通過(guò)建立一個(gè)DD client(DD系統(tǒng)提供的一套DD訪問(wèn)框架)來(lái)訪問(wèn)DD,具體流程為通過(guò)與線程THD綁定的類Dictionary_client,來(lái)依次訪問(wèn)一級(jí)緩存和二級(jí)緩存,如果兩級(jí)緩存中都沒(méi)有要訪問(wèn)的DD對(duì)象,則會(huì)直接去存儲(chǔ)在InnoDB的DD tables中去讀取。后文會(huì)詳細(xì)介紹這個(gè)過(guò)程。

DD的兩級(jí)緩存底層都是基于std::map,即鍵值對(duì)來(lái)實(shí)現(xiàn)的。

  • 第一級(jí)緩存是本地緩存,由每個(gè)DD client線程獨(dú)享,核心數(shù)據(jù)結(jié)構(gòu)為L(zhǎng)ocal_multi_map,用于加速當(dāng)前線程對(duì)于同一對(duì)象的重復(fù)訪問(wèn),以及在當(dāng)前線程執(zhí)行DDL語(yǔ)句修改DD對(duì)象時(shí)管理已提交、未提交、刪除狀態(tài)的對(duì)象。
  • 第二級(jí)緩存是共享緩存,為所有線程共享的全局緩存,核心數(shù)據(jù)結(jié)構(gòu)為Shared_multi_map,保存著所有線程都可以訪問(wèn)到的對(duì)象,因此其中包含一些并發(fā)控制的處理。

整個(gè)DD cache的相關(guān)類圖結(jié)構(gòu)如下:

圖2 數(shù)據(jù)字典緩存類圖

Element_map是對(duì)std::map的一個(gè)封裝,鍵是id、name等,值是Cache_element,它包含了DD cache object,以及對(duì)該對(duì)象的引用計(jì)數(shù)。DD cache object就是我們要獲取的DD信息。

Multi_map_base中包含了多個(gè)Element_map,可以讓用戶根據(jù)不同類型的key來(lái)獲取緩存對(duì)象。Local_multi_map和Shared_multi_map都是繼承于Multi_map_base。

兩級(jí)緩存

第一級(jí)緩存,即本地緩存,位于每個(gè)Dictionary_client內(nèi)部,由不同狀態(tài)(committed、uncommitted、dropped)的Object_registry組成。

class Dictionary_client {
 private:
  std::vector<Entity_object *> m_uncached_objects;  // Objects to be deleted.
  Object_registry m_registry_committed;    // Registry of committed objects.
  Object_registry m_registry_uncommitted;  // Registry of uncommitted objects.
  Object_registry m_registry_dropped;      // Registry of dropped objects.
  THD *m_thd;                        // Thread context, needed for cache misses.
  ...
};

代碼段1

其中m_registry_committed,存放的是DD client訪問(wèn)DD時(shí)已經(jīng)提交且可見(jiàn)的DD cache object。如果DD client所在的當(dāng)前線程執(zhí)行的是一條DDL語(yǔ)句,則會(huì)在執(zhí)行過(guò)程中將要drop的舊表對(duì)應(yīng)的DD cache object存放在m_registry_dropped中,將還未提交的新表定義對(duì)應(yīng)的DD cache object存放在m_registry_uncommitted中。在事務(wù)commit/rollback后,會(huì)把m_registry_uncommitted中的DD cache object更新到m_registry_committed中去,并把m_registry_uncommitted和m_registry_dropped清空。

每個(gè)Object_registry由不同元數(shù)據(jù)類型的Local_multi_map組成,通過(guò)模板的方式,實(shí)現(xiàn)對(duì)不同類型的對(duì)象(比如表、schema、tablespace、Event 等)緩存的管理。

第二級(jí)緩存,即共享緩存,是全局唯一的,使用單例Shared_dictionary_cache來(lái)實(shí)現(xiàn)。

Shared_dictionary_cache *Shared_dictionary_cache::instance() {
  static Shared_dictionary_cache s_cache;
  return &s_cache;
}

代碼段2

與本地緩存中Object_registry相似,Shared_dictionary_cache也包含針對(duì)各種類型對(duì)象的緩存。與本地緩存的區(qū)別在于,本地緩存可以無(wú)鎖訪問(wèn),而共享緩存需要在獲取/釋放DD cache object時(shí)進(jìn)行加鎖來(lái)完成并發(fā)控制,并會(huì)通過(guò)Shared_multi_map中的條件變量來(lái)完成并發(fā)訪問(wèn)中的線程同步與緩存未命中情況的處理。

緩存讀取過(guò)程

邏輯流程

DD對(duì)象主要有兩種訪問(wèn)方式,即通過(guò)元數(shù)據(jù)的id,或者name來(lái)訪問(wèn)。需要訪問(wèn)DD的數(shù)據(jù)庫(kù)工作線程通過(guò)DD client,傳入元數(shù)據(jù)的id,name等key去緩存中讀取元數(shù)據(jù)對(duì)象。讀取的整體過(guò)程:一級(jí)本地緩存 -> 二級(jí)共享緩存 -> 存儲(chǔ)引擎。流程圖如下:

圖3 數(shù)據(jù)字典緩存讀取流程圖

由上圖所示,在DD cache object加入到一級(jí)緩存時(shí),已經(jīng)確保其在二級(jí)緩存中也備份了一份,以供其他線程使用。

代碼實(shí)現(xiàn)如下:

// Get a dictionary object.
template <typename K, typename T>
bool Dictionary_client::acquire(const K &key, const T **object,
                                bool *local_committed,
                                bool *local_uncommitted) {
  ...
  // Lookup in registry of uncommitted objects
  T *uncommitted_object = nullptr;
  bool dropped = false;
  acquire_uncommitted(key, &uncommitted_object, &dropped);
  ...
  // Lookup in the registry of committed objects.
  Cache_element<T> *element = NULL;
  m_registry_committed.get(key, &element);
  ...
  // Get the object from the shared cache.
  if (Shared_dictionary_cache::instance()->get(m_thd, key, &element)) {
    DBUG_ASSERT(m_thd->is_system_thread() || m_thd->killed ||
                m_thd->is_error());
    return true;
  }
  ...
}

代碼段3

在一級(jí)本地緩存中讀取時(shí),會(huì)先去m_registry_uncommitted和m_registry_dropped中讀?。ň赼cquire_uncommitted()函數(shù)中實(shí)現(xiàn)),因?yàn)檫@兩個(gè)是最新的修改。之后再去m_registry_committed中讀取,如果讀取到就直接返回,否則去二級(jí)共享緩存中嘗試讀取。共享緩存的讀取過(guò)程在Shared_multi_map::get()中實(shí)現(xiàn)。就是加鎖后直接到對(duì)應(yīng)的Element_map中查找,存在則把其加入到一級(jí)緩存中并返回;不存在,則會(huì)進(jìn)入到緩存未命中的處理流程。

緩存未命中

當(dāng)本地緩存和共享緩存中都沒(méi)有讀取到元數(shù)據(jù)對(duì)象時(shí),就會(huì)調(diào)用DD cache的持久化存儲(chǔ)的接口Storage_adapter::get()直接從存儲(chǔ)在InnoDB中的DD tables中讀取,創(chuàng)建出DD cache object后,依次把其加入到共享緩存和本地緩存中。

DD client對(duì)并發(fā)訪問(wèn)未命中緩存的情況做了并發(fā)控制,這樣做有以下幾個(gè)考量:

1.因?yàn)閮?nèi)存對(duì)象可以共用,所以只需要維護(hù)一個(gè)DD cache object在內(nèi)存即可。

2.訪問(wèn)持久化存儲(chǔ)的調(diào)用棧較深,可能涉及IO,比較耗時(shí)。

3.不需要每個(gè)線程都去持久化存儲(chǔ)中讀取數(shù)據(jù),避免資源的浪費(fèi)。

并發(fā)控制的代碼如下:

// Get a wrapper element from the map handling the given key type.
template <typename T>
template <typename K>
bool Shared_multi_map<T>::get(const K &key, Cache_element<T> **element) {
  Autolocker lock(this);
  *element = use_if_present(key);
  if (*element) return false;
  // Is the element already missed?
  if (m_map<K>()->is_missed(key)) {
    while (m_map<K>()->is_missed(key))
      mysql_cond_wait(&m_miss_handled, &m_lock);
    *element = use_if_present(key);
    // Here, we return only if element is non-null. An absent element
    // does not mean that the object does not exist, it might have been
    // evicted after the thread handling the first cache miss added
    // it to the cache, before this waiting thread was alerted. Thus,
    // we need to handle this situation as a cache miss if the element
    // is absent.
    if (*element) return false;
  }
  // Mark the key as being missed.
  m_map<K>()->set_missed(key);
  return true;
}

代碼段4

第一個(gè)訪問(wèn)未命中緩存的DD client會(huì)將key加入到Shared_multi_map的m_missed集合中,這個(gè)集合包含著現(xiàn)在所有正在讀取DD table中元數(shù)據(jù)的對(duì)象key值。之后的client在訪問(wèn)DD table之前會(huì)先判斷目標(biāo)key值是否在m_missed集合中,如在,就會(huì)進(jìn)入等待。當(dāng)?shù)谝粋€(gè)DD client構(gòu)建好DD cache object,并把其加入到共享緩存之后,移除m_missed集合中對(duì)應(yīng)的key,并通過(guò)條件變量通知所有等待的線程重新在共享緩存中獲取。這樣對(duì)于同一個(gè)DD cache object,就只會(huì)對(duì)DD table訪問(wèn)一次了。時(shí)序圖如下:

圖4 數(shù)據(jù)字典緩存未命中時(shí)序圖

緩存修改過(guò)程

在一個(gè)數(shù)據(jù)庫(kù)工作線程對(duì)DD進(jìn)行修改時(shí),DD cache也會(huì)在事務(wù)commit階段通過(guò)remove_uncommitted_objects()函數(shù)進(jìn)行更新,更新的過(guò)程為先把DD舊數(shù)據(jù)從緩存中刪除,再把修改后的DD cache object更新到緩存中去,先更新二級(jí)緩存,再更新一級(jí)緩存,流程圖如下:

圖5 數(shù)據(jù)字典緩存更新流程圖

因?yàn)檫@個(gè)更新DD緩存的操作是在事務(wù)commit階段進(jìn)行,所以在更新一級(jí)緩存時(shí),會(huì)先把更新后的DD cache object放到一級(jí)緩存中的m_registry_committed里去,再把m_registry_uncommitted和m_registry_dropped清空。

緩存失效過(guò)程

當(dāng)Dictionary_client的drop方法被調(diào)用對(duì)元數(shù)據(jù)對(duì)象進(jìn)行清理時(shí),在元數(shù)據(jù)對(duì)象從DD tables中刪除后,會(huì)調(diào)用invalidate()函數(shù)使兩級(jí)緩存中的DD cache object失效。流程圖如下:

圖6 數(shù)據(jù)字典緩存失效流程圖

這里在判斷DD cache object在一級(jí)緩存中存在,并在一級(jí)緩存中刪除掉該對(duì)象后,可以直接在二級(jí)緩存中完成刪除操作。緩存失效的過(guò)程受到元數(shù)據(jù)鎖(Metadata lock, MDL)的保護(hù),因?yàn)樵獢?shù)據(jù)鎖的并發(fā)控制,保證了一個(gè)線程在刪除共享緩存時(shí),不會(huì)有其他線程也來(lái)刪除它。實(shí)際上本地緩存的數(shù)據(jù)有效,就是依賴于元數(shù)據(jù)鎖的保護(hù),否則共享緩存區(qū)域的信息,是可以被其他線程更改的。

緩存容量管理

一級(jí)本地緩存為DD client線程獨(dú)享,由RAII類Auto_releaser來(lái)負(fù)責(zé)管理其生命周期。其具體流程為:每次建立一個(gè)DD client時(shí),會(huì)定義一個(gè)對(duì)應(yīng)的Auto_releaser類,當(dāng)訪問(wèn)DD時(shí),會(huì)把讀取到的DD cache object同時(shí)加到Auto_releaser里面的m_release_registry中去,當(dāng)Auto_releaser析構(gòu)時(shí),會(huì)調(diào)用Dictionary_client的release()函數(shù)把m_release_registry中的DD緩存全部釋放掉。

二級(jí)共享緩存會(huì)在Shared_dictionary_cache初始化時(shí),根據(jù)不同類型的對(duì)象設(shè)定好緩存的容量,代碼如下:

void Shared_dictionary_cache::init() {
  instance()->m_map<Collation>()->set_capacity(collation_capacity);
  instance()->m_map<Charset>()->set_capacity(charset_capacity);
  ...
}

代碼段5

在二級(jí)緩存容量達(dá)到上限時(shí),會(huì)通過(guò)LRU的緩存淘汰策略來(lái)淘汰最近最少使用的DD cache對(duì)象。在一級(jí)緩存中存在的緩存對(duì)象不會(huì)被淘汰。

// Helper function to evict unused elements from the free list.
template <typename T>
void Shared_multi_map<T>::rectify_free_list(Autolocker *lock) {
  mysql_mutex_assert_owner(&m_lock);
  while (map_capacity_exceeded() && m_free_list.length() > 0) {
    Cache_element<T> *e = m_free_list.get_lru();
    DBUG_ASSERT(e && e->object());
    m_free_list.remove(e);
    // Mark the object as being used to allow it to be removed.
    e->use();
    remove(e, lock);
  }
}

代碼段6

總結(jié)

MySQL 8.0中的數(shù)據(jù)字典,通過(guò)對(duì)兩級(jí)緩存的逐級(jí)訪問(wèn),以及精妙的對(duì)緩存未命中情況的處理方式,有效的加速了在不同場(chǎng)景下數(shù)據(jù)庫(kù)對(duì)DD的訪問(wèn)速度,顯著的提升了數(shù)據(jù)庫(kù)訪問(wèn)元數(shù)據(jù)信息的效率。另外本文還提到了元數(shù)據(jù)鎖對(duì)數(shù)據(jù)字典緩存的保護(hù),關(guān)于元數(shù)據(jù)鎖的相關(guān)機(jī)制,會(huì)在后續(xù)文章陸續(xù)介紹。

到此這篇關(guān)于解讀MySQL 8.0數(shù)據(jù)字典緩存管理機(jī)制的文章就介紹到這了,更多相關(guān)MySQL數(shù)據(jù)字典內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • sql四大排名函數(shù)之ROW_NUMBER、RANK、DENSE_RANK、NTILE使用介紹

    sql四大排名函數(shù)之ROW_NUMBER、RANK、DENSE_RANK、NTILE使用介紹

    這篇文章主要介紹了sql四大排名函數(shù)之ROW_NUMBER、RANK、DENSE_RANK、NTILE使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Navicat導(dǎo)入mysql數(shù)據(jù)庫(kù)的圖文教程

    Navicat導(dǎo)入mysql數(shù)據(jù)庫(kù)的圖文教程

    本文主要介紹了Navicat導(dǎo)入mysql數(shù)據(jù)庫(kù)的圖文教程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • MySQL實(shí)現(xiàn)批量推送數(shù)據(jù)到Mongo

    MySQL實(shí)現(xiàn)批量推送數(shù)據(jù)到Mongo

    這篇文章主要為大家詳細(xì)介紹了MySQL如何實(shí)現(xiàn)批量推送數(shù)據(jù)到Mongo,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,感興趣的可以了解一下
    2023-05-05
  • MySQL中的樂(lè)觀鎖,悲觀鎖和MVCC全面解析

    MySQL中的樂(lè)觀鎖,悲觀鎖和MVCC全面解析

    這篇文章主要介紹了MySQL中的樂(lè)觀鎖和悲觀鎖和MVCC全面解析的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)MySQL數(shù)據(jù)庫(kù),感興趣的朋友可以了解下
    2021-01-01
  • Mysql命令行導(dǎo)入sql數(shù)據(jù)

    Mysql命令行導(dǎo)入sql數(shù)據(jù)

    下面是在命令行下導(dǎo)入sql數(shù)據(jù)的方法,需要的朋友可以參考下。
    2010-03-03
  • You must SET PASSWORD before executing this statement的解決方法

    You must SET PASSWORD before execut

    今天在MySql5.6操作時(shí)報(bào)錯(cuò):You must SET PASSWORD before executing this statement解決方法,需要的朋友可以參考下
    2013-06-06
  • MySQL中查詢、刪除重復(fù)記錄的方法大全

    MySQL中查詢、刪除重復(fù)記錄的方法大全

    mysql中刪除重復(fù)記錄的方法有很多種,下面這篇文章主要給大家總結(jié)了在MySQL中查詢、刪除重復(fù)記錄的方法大全,文中給出了詳細(xì)的示例代碼供大家參考學(xué)習(xí),需要的朋友下面來(lái)一起看看吧。
    2017-06-06
  • MySQL之高可用集群部署及故障切換實(shí)現(xiàn)

    MySQL之高可用集群部署及故障切換實(shí)現(xiàn)

    這篇文章主要介紹了MySQL之高可用集群部署及故障切換實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • MySql執(zhí)行流程與生命周期詳解

    MySql執(zhí)行流程與生命周期詳解

    當(dāng)你執(zhí)行一次MySQL查詢時(shí),有沒(méi)有仔細(xì)想過(guò),在查詢結(jié)果返回之前,經(jīng)過(guò)了哪些步驟呢?這些步驟有可能消耗了超出想象的時(shí)間和資源。因此,在對(duì)MySQL的查詢進(jìn)行優(yōu)化之前,應(yīng)該了解一下MySQL查詢的生命周期
    2022-09-09
  • 重新restore了mysql到另一臺(tái)機(jī)器上后mysql 編碼問(wèn)題報(bào)錯(cuò)

    重新restore了mysql到另一臺(tái)機(jī)器上后mysql 編碼問(wèn)題報(bào)錯(cuò)

    重新restore了mysql到另一臺(tái)機(jī)器上,今天新寫(xiě)了一個(gè)app,發(fā)現(xiàn)在admin界面下一添加漢字就會(huì)報(bào)錯(cuò)
    2011-12-12

最新評(píng)論