由于本人水平有限,有錯誤的地方還請大家幫忙指正.
我們知道MySQL是一個插件式存儲引擎的數據庫,不同存儲引擎的對象的元數據的存儲方式是不一樣的.例如:InnoDB的表的元數據信息都是存儲在SYS_TABLES和SYS_INDEXES等數據字典中,數據結構也是dict_table_t 、dict_index_t等結構體,而MyISAM的表結構只有.frm文件存儲.那么MySQL Server層怎樣識別以及使用不同Engine的對象結構呢?
-
TABLE_SHARE
MySQL Server層在緩存不同Engine的表對象過程中使用TABLE_SHARE的結構體,這里是不區分任何存儲引擎的表結構,并且每一個表名(帶模式名即庫名)都一一對應一個TABLE_SHARE結構體對象.
TABLE_SHARE結構體主要成員如下:
struct TABLE_SHARE{ TABLE_CATEGORY table_category; //表的類型 ... Field **field; //表的field字段 KEY *key_info; //表定義的KEY信息(即索引信息) LEX_STRING table_cache_key; //TABLE_SHARE對象在table_cache中的key LEX_STRING db; //表所在的DB name LEX_STRING table_name; //表名 LEX_STRING path; //.frm文件路徑名 // 指向每個table_cache包含該表的el地址 數組大小等于table_cache的數組大小 Table_cache_element **cache_element; ...ulong version; //TABLE_SHARE的版本 如果版本變了 必須重新reopenulong mysql_version; /* 0 if .frm is created before 5.0 */ulong reclength; //記錄長度ulong stored_rec_length; //存儲的記錄長度uint ref_count; // TABLE 對象在使用的個數 即存在多少個TABLE對象 plugin_ref db_plugin; //存儲引擎對象指針 ...}
當MySQL Server層在open table時,需要從frm文件(不區分存儲引擎)中將這個表的表名、庫名、所有的列信息、列的默認值、表的字符集、對應的.frm文件路徑、所屬的Engine、索引等信息存儲到TABLE_SHARE結構體對象中,然后TABLE_SHARE對象在table_def_cache中緩存.(open_table_def中完成從frm到TABLE_SHARE寫入)
-
TABLE
當我們獲得TABLE_SHARE的對象之后,該如何使用TABLE_SHARE對象呢?同一時刻可能存在多個不同的session同時訪問同一個表進行不同的操作,那么怎樣保證每個不同的session的對象都是獨立的互不影響的呢?
每個連接到MySQL Server層的thread在獲得TABLE_SHARE對象之后(所有的線程可以共用一個TABLE_SHARE對象),都會創建一個TABLE結構體的對象,這個對象是該thread在使用期間獨占的.(open_table_from_share )
TABLE結構體精簡如下:
struct TABLE{TABLE_SHARE *s; //TABLE_SHARE對象指針handler *file; //存儲引擎句柄 對存儲引擎的操作通過該對象指針操作TABLE *next, *prev; //TABLE對象前后節點指針private:TABLE *cache_next, **cache_prev;//用于table_cache中的list鏈表節點指針friend class Table_cache_element; //用于訪問cache_next和cache_prev兩個成員public:THD *in_use; //thread 對象指針Field **field; //表的列的存儲對象 同TABLE_SHARE中的列uchar *record[2]; //記錄數據的存儲地址uchar *write_row_record; //THE::write_row中優化使用uchar *insert_values; //用于INSERT ... UPDATE....KEY *key_info; //表定義的KEY信息 同TABLE_SHARE....};
-
Table_cache管理
MySQL Server層對TABLE對象的管理主要通過table_cache_manager完成,主要結構如下:
class Table_cache_element{// 緩存一個表名的所有TABLE對象 一個element對象只緩存一個表名的所有TABLEprivate:typedef I_P_List <TABLE,I_P_List_adapter<TABLE,&TABLE::cache_next,&TABLE::cache_prev> > TABLE_list;TABLE_list used_tables; // 正在使用的TABLE對象TABLE_list free_tables; // 可以直接使用的TABLE 對象TABLE_SHARE *share; // TABLE_SHARE 緩存對象}class Table_cache{// 緩存TABLE對象 一個Table_cache包含N個不同表名的TABLE對象HASH m_cache; // The hash of Table_cache_element objects,Table_cache_element::share::table_cache_ke作為hash的keyTABLE *m_unused_tables; // 所有unused的TABLE對象};class Table_cache_manager{Table_cache m_table_cache[MAX_TABLE_CACHES]; // table_cache對象數組}extern Table_cache_manager table_cache_manager; // table_cache全局管理對象HASH table_def_cache // 緩存TABLE_SHARE的hash表
table_cache的精簡架構圖如下:

|
一個thread怎樣獲得緩存的TABLE* 對象: 1)根據thread_id%table_cache_instances 獲得tc對象,假設為tc1 2)根據key找到對應的el對象,假設為el1 3)獲得el1中free_tables的TABLE* 對象 |
如果已經創建了一個TABLE*對象,那怎樣快速知道一個TABLE* 是屬于哪個el的呢而加入到對應的used_tables鏈表中呢?
TABLE_SHARE存在一個el*的s數組,數組的大小為table_cache_instances個數,如下圖:

假設thread獲得的為tc1和el1,那么cache_element[1]存儲的為tc1->el1對象.
el= table->s->cache_element[table_cache_manager.cache_index(thd->id)];
-
open_table和close_table
open_table:
接下來看MySQLServer層在open表的過程中,加載表結構.frm文件,轉換TABLE對象的主要步驟.
open_table|->get_table_def_key //庫名.表名 得到對應的key|->retry_share:|->Table_cache *tc = table_cache_manager::get_cache(thd)| //首先根據thd的m_thread_id%table_cache_instances 獲取table_cache_manager.m_table_cache[i]|->table = tc->get_table(thd, key, key_length, &share)| |->el_it = tc->m_cache.find(key_str) //一個key對應一個el 一個el對應一個TABLE_SHARE* N個TABLE*| |->if(el_it == m_cache.end()) return NULL| |->*share = el->share| |->if((table = el->free_tables.front()))//從el的free_tables鏈表獲取TABLE*對象| | |->el->free_tables.remove(table)| | |->el->used_tables.push_front(table) //close_thread時候會將table從el的used_tables移除,加入free_tables| |->return table|->get_table_share_with_discover| |->get_table_share| |->my_hash_search_using_hash_value從table_def_cache中HASH查找TABLE_SHARE| |->alloc_table_share 新建share變量賦值share的frm路徑信息| |->my_hash_insert //share插入hash表table_def_cache中| |->open_table_def| | |->open_binary_frm //讀取.frm文件賦值engine類型、KEY、FIELD信息| | |->legacy_db_type= (enum legacy_db_type) (uint) *(head+3);//獲得engine類型| | |->share->db_plugin= ha_lock_engine //根據engine類型獲得plugin對象 初始化在innobase_init中完成| |->// 若table_def_cache記錄超過table_def_size 則從hash刪除oldest_unused_share|->share_found:|->if (!(flags & MYSQL_OPEN_IGNORE_FLUSH))| |->if (share->has_old_version())| |->release_table_share(share) // 如果TABLE_SHARE沒有引用 則從table_def_cache中刪除| |->tdc_wait_for_old_version(lock_wait_timeout)| | |->TABLE_SHARE::wait_for_old_version| | |->m_flush_tickets.push_front(&ticket)// 增加ticket到SHARE的m_flush_tickets鏈表中| | |->thd->mdl_context->m_wait.timed_wait(thd,wait) // MDL_wait::timed_wait等待其他線程喚醒| |->goto retry_share // 喚醒之后再次獲取TABLE_SHARE|->open_table_from_share| |->outparam->file=get_new_handler(share->db_type())| | |->file= db_type->create(db_type, share, alloc)| | |-> file->table_share=share file->ht=db_type //innobase_create_handler創建handler對象| | |->file->init() //handler::init()| |->//copy share中的key、column信息賦值到TABLE中| |->outparam->file->ha_open(TABLE,table_name,mode)| | |-> file->table=outparam //handler::ha_open| |->ha_innobase::open(table_name)| |->ib_table=open_dict_table(table_name,...,)//根據name加載innodb的系統表中的表的元數據| |->m_prebuilt=row_create_prebuilt(ib_table,TABLE->S->reclength)//創建prebuit對象在查詢數據時的元組結構|->Table_cache::add_used_table //將TABLE對象加入Table_cache中| |->el=table->s->cache_element[this->idx] // 獲取該表名在該table_cache中的el| |->if(!el) // 如果不存在| | |->el= new Table_cache_element(table->s) // 創建el對象| | |->my_hash_insert(&m_cache, (uchar*)el) // 加入當前table_cache的el的hash鏈表| | |->table->s->cache_element[this->idx]=el // 存到TABLE_SHARE對應的el位置 其他線程訪問當前table_cache使用| |->el->used_tables.push_front(table) // TABLE對象加入el的used鏈表| |->m_table_count++|->table_found:|->thd->set_open_tables(table)|->table->init(thd, table_list)
流程解釋:
1)首先根據thd的thread_id找到對應的tc,然后根據key定位找到tc中緩存的el,如果el的share和TABLE都存在,則直接使用TABLE對象.
2)如果el的share存在,無緩存的TABLE對象,則調用open_table_from_share,根據share構造TABLE對象,并加入used_tables鏈表.
3)如果el的share不存在,則先調用get_table_share_with_discover根據frm文件構造share對象,然后再構造TABLE對象.
close_table:
我們看下當一個thd一條語句執行完畢之后,對于打開的TABLE對象如何處理
mysql_execute_command|->close_thread_tables| |->while (thd->open_tables)| |->close_open_tables(thd, &thd->open_tables)| |->close_thread_table| |->if(!table->needs_reopen())ha_innobase::reset()//reset某些成員變量| |->tc->lock();| |->if(table->s->has_old_version()||table->needs_reopen())| | |->tc->remove_table(table) //TABLE* 從tc緩存中刪除| | |->intern_close_table(table) //close TABLE->file 釋放file對應的內存對象同時free_table_share釋放share內存對象| |->else| | |->tc->release_table(thd, table)//不close TABLE->file| | |->table->in_use = nullptr| | |->//將TABLE* 指針對象從el->used_tables移除加入el->free_tables鏈表同時加入tc->m_unused_tables| |->tc->unlock()|->MDL_context::release_transactional_locks //釋放所有的MDL鎖intern_close_table(TABLE *table)|->closefrm(table, free_share=1)|->table->file->ha_close()|->release_table_share(table->s)|->if(!--share->ref_count)//使用share open TABLE才會+1|->if(share->has_old_version())|->my_hash_delete(&table_def_cache, (uchar*) share)|->table_def_free_entry(share)|->free_table_share(share)|->while(ticket=share->m_flush_tickets)|->ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED) // 喚醒其他等待old_version的thread
|
注: TABLE_SHARE::m_version在alloc_table_share初始化為refresh_version 在DDL過程中會調用TABLE_SHARE::clear_version()將m_version重置為0 |
從上面的過程我們可以知道:
| TABLE對象已經不僅僅是MySQL Server層的對象了,它是可以具體操作某一個存儲引擎的對象,所以TABLE對象作為MySQL層和存儲引擎之間的橋梁,可以直接使用TABLE對象的存儲引擎的句柄執行各個公共C++ API來操作具體執行動作. |
總結:
通過對源碼的淺析,我們已經基本了解了MySQL Server層怎樣通過一步步獲得不同存儲引擎表結構的信息,希望對大家理解MySQL Server層對象的緩存有一定的幫助.






