由于本人水平有限,有錯(cuò)誤的地方還請(qǐng)大家?guī)兔χ刚?
我們知道MySQL是一個(gè)插件式存儲(chǔ)引擎的數(shù)據(jù)庫,不同存儲(chǔ)引擎的對(duì)象的元數(shù)據(jù)的存儲(chǔ)方式是不一樣的.例如:InnoDB的表的元數(shù)據(jù)信息都是存儲(chǔ)在SYS_TABLES和SYS_INDEXES等數(shù)據(jù)字典中,數(shù)據(jù)結(jié)構(gòu)也是dict_table_t 、dict_index_t等結(jié)構(gòu)體,而MyISAM的表結(jié)構(gòu)只有.frm文件存儲(chǔ).那么MySQL Server層怎樣識(shí)別以及使用不同Engine的對(duì)象結(jié)構(gòu)呢?
-
TABLE_SHARE
MySQL Server層在緩存不同Engine的表對(duì)象過程中使用TABLE_SHARE的結(jié)構(gòu)體,這里是不區(qū)分任何存儲(chǔ)引擎的表結(jié)構(gòu),并且每一個(gè)表名(帶模式名即庫名)都一一對(duì)應(yīng)一個(gè)TABLE_SHARE結(jié)構(gòu)體對(duì)象.
TABLE_SHARE結(jié)構(gòu)體主要成員如下:
struct TABLE_SHARE
{
TABLE_CATEGORY table_category; //表的類型
...
Field **field; //表的field字段
KEY *key_info; //表定義的KEY信息(即索引信息)
LEX_STRING table_cache_key; //TABLE_SHARE對(duì)象在table_cache中的key
LEX_STRING db; //表所在的DB name
LEX_STRING table_name; //表名
LEX_STRING path; //.frm文件路徑名
// 指向每個(gè)table_cache包含該表的el地址 數(shù)組大小等于table_cache的數(shù)組大小
Table_cache_element **cache_element;
...
ulong version; //TABLE_SHARE的版本 如果版本變了 必須重新reopen
ulong mysql_version; /* 0 if .frm is created before 5.0 */
ulong reclength; //記錄長(zhǎng)度
ulong stored_rec_length; //存儲(chǔ)的記錄長(zhǎng)度
uint ref_count; // TABLE 對(duì)象在使用的個(gè)數(shù) 即存在多少個(gè)TABLE對(duì)象
plugin_ref db_plugin; //存儲(chǔ)引擎對(duì)象指針
...
}
當(dāng)MySQL Server層在open table時(shí),需要從frm文件(不區(qū)分存儲(chǔ)引擎)中將這個(gè)表的表名、庫名、所有的列信息、列的默認(rèn)值、表的字符集、對(duì)應(yīng)的.frm文件路徑、所屬的Engine、索引等信息存儲(chǔ)到TABLE_SHARE結(jié)構(gòu)體對(duì)象中,然后TABLE_SHARE對(duì)象在table_def_cache中緩存.(open_table_def中完成從frm到TABLE_SHARE寫入)
-
TABLE
當(dāng)我們獲得TABLE_SHARE的對(duì)象之后,該如何使用TABLE_SHARE對(duì)象呢?同一時(shí)刻可能存在多個(gè)不同的session同時(shí)訪問同一個(gè)表進(jìn)行不同的操作,那么怎樣保證每個(gè)不同的session的對(duì)象都是獨(dú)立的互不影響的呢?
每個(gè)連接到MySQL Server層的thread在獲得TABLE_SHARE對(duì)象之后(所有的線程可以共用一個(gè)TABLE_SHARE對(duì)象),都會(huì)創(chuàng)建一個(gè)TABLE結(jié)構(gòu)體的對(duì)象,這個(gè)對(duì)象是該thread在使用期間獨(dú)占的.(open_table_from_share )
TABLE結(jié)構(gòu)體精簡(jiǎn)如下:
struct TABLE
{
TABLE_SHARE *s; //TABLE_SHARE對(duì)象指針
handler *file; //存儲(chǔ)引擎句柄 對(duì)存儲(chǔ)引擎的操作通過該對(duì)象指針操作
TABLE *next, *prev; //TABLE對(duì)象前后節(jié)點(diǎn)指針
private:
TABLE *cache_next, **cache_prev;//用于table_cache中的list鏈表節(jié)點(diǎn)指針
friend class Table_cache_element; //用于訪問cache_next和cache_prev兩個(gè)成員
public:
THD *in_use; //thread 對(duì)象指針
Field **field; //表的列的存儲(chǔ)對(duì)象 同TABLE_SHARE中的列
uchar *record[2]; //記錄數(shù)據(jù)的存儲(chǔ)地址
uchar *write_row_record; //THE::write_row中優(yōu)化使用
uchar *insert_values; //用于INSERT ... UPDATE
....
KEY *key_info; //表定義的KEY信息 同TABLE_SHARE
....
};
-
Table_cache管理
MySQL Server層對(duì)TABLE對(duì)象的管理主要通過table_cache_manager完成,主要結(jié)構(gòu)如下:
class Table_cache_element
{// 緩存一個(gè)表名的所有TABLE對(duì)象 一個(gè)element對(duì)象只緩存一個(gè)表名的所有TABLE
private:
typedef I_P_List <TABLE,
I_P_List_adapter<TABLE,
&TABLE::cache_next,
&TABLE::cache_prev> > TABLE_list;
TABLE_list used_tables; // 正在使用的TABLE對(duì)象
TABLE_list free_tables; // 可以直接使用的TABLE 對(duì)象
TABLE_SHARE *share; // TABLE_SHARE 緩存對(duì)象
}
class Table_cache
{// 緩存TABLE對(duì)象 一個(gè)Table_cache包含N個(gè)不同表名的TABLE對(duì)象
HASH m_cache; // The hash of Table_cache_element objects,Table_cache_element::share::table_cache_ke作為hash的key
TABLE *m_unused_tables; // 所有unused的TABLE對(duì)象
};
class Table_cache_manager
{
Table_cache m_table_cache[MAX_TABLE_CACHES]; // table_cache對(duì)象數(shù)組
}
extern Table_cache_manager table_cache_manager; // table_cache全局管理對(duì)象
HASH table_def_cache // 緩存TABLE_SHARE的hash表
table_cache的精簡(jiǎn)架構(gòu)圖如下:
一個(gè)thread怎樣獲得緩存的TABLE* 對(duì)象: 1)根據(jù)thread_id%table_cache_instances 獲得tc對(duì)象,假設(shè)為tc1 2)根據(jù)key找到對(duì)應(yīng)的el對(duì)象,假設(shè)為el1 3)獲得el1中free_tables的TABLE* 對(duì)象 |
如果已經(jīng)創(chuàng)建了一個(gè)TABLE*對(duì)象,那怎樣快速知道一個(gè)TABLE* 是屬于哪個(gè)el的呢而加入到對(duì)應(yīng)的used_tables鏈表中呢?
TABLE_SHARE存在一個(gè)el*的s數(shù)組,數(shù)組的大小為table_cache_instances個(gè)數(shù),如下圖:
假設(shè)thread獲得的為tc1和el1,那么cache_element[1]存儲(chǔ)的為tc1->el1對(duì)象.
el= table->s->cache_element[table_cache_manager.cache_index(thd->id)];
-
open_table和close_table
open_table:
接下來看MySQLServer層在open表的過程中,加載表結(jié)構(gòu).frm文件,轉(zhuǎn)換TABLE對(duì)象的主要步驟.
open_table
|->get_table_def_key //庫名.表名 得到對(duì)應(yīng)的key
|->retry_share:
|->Table_cache *tc = table_cache_manager::get_cache(thd)
| //首先根據(jù)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) //一個(gè)key對(duì)應(yīng)一個(gè)el 一個(gè)el對(duì)應(yīng)一個(gè)TABLE_SHARE* N個(gè)TABLE*
| |->if(el_it == m_cache.end()) return NULL
| |->*share = el->share
| |->if((table = el->free_tables.front()))//從el的free_tables鏈表獲取TABLE*對(duì)象
| | |->el->free_tables.remove(table)
| | |->el->used_tables.push_front(table) //close_thread時(shí)候會(huì)將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 //根據(jù)engine類型獲得plugin對(duì)象 初始化在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創(chuàng)建handler對(duì)象
| | |->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,...,)//根據(jù)name加載innodb的系統(tǒng)表中的表的元數(shù)據(jù)
| |->m_prebuilt=row_create_prebuilt(ib_table,TABLE->S->reclength)//創(chuàng)建prebuit對(duì)象在查詢數(shù)據(jù)時(shí)的元組結(jié)構(gòu)
|->Table_cache::add_used_table //將TABLE對(duì)象加入Table_cache中
| |->el=table->s->cache_element[this->idx] // 獲取該表名在該table_cache中的el
| |->if(!el) // 如果不存在
| | |->el= new Table_cache_element(table->s) // 創(chuàng)建el對(duì)象
| | |->my_hash_insert(&m_cache, (uchar*)el) // 加入當(dāng)前table_cache的el的hash鏈表
| | |->table->s->cache_element[this->idx]=el // 存到TABLE_SHARE對(duì)應(yīng)的el位置 其他線程訪問當(dāng)前table_cache使用
| |->el->used_tables.push_front(table) // TABLE對(duì)象加入el的used鏈表
| |->m_table_count++
|->table_found:
|->thd->set_open_tables(table)
|->table->init(thd, table_list)
流程解釋:
1)首先根據(jù)thd的thread_id找到對(duì)應(yīng)的tc,然后根據(jù)key定位找到tc中緩存的el,如果el的share和TABLE都存在,則直接使用TABLE對(duì)象.
2)如果el的share存在,無緩存的TABLE對(duì)象,則調(diào)用open_table_from_share,根據(jù)share構(gòu)造TABLE對(duì)象,并加入used_tables鏈表.
3)如果el的share不存在,則先調(diào)用get_table_share_with_discover根據(jù)frm文件構(gòu)造share對(duì)象,然后再構(gòu)造TABLE對(duì)象.
close_table:
我們看下當(dāng)一個(gè)thd一條語句執(zhí)行完畢之后,對(duì)于打開的TABLE對(duì)象如何處理
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對(duì)應(yīng)的內(nèi)存對(duì)象同時(shí)free_table_share釋放share內(nèi)存對(duì)象
| |->else
| | |->tc->release_table(thd, table)//不close TABLE->file
| | |->table->in_use = nullptr
| | |->//將TABLE* 指針對(duì)象從el->used_tables移除加入el->free_tables鏈表同時(shí)加入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才會(huì)+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過程中會(huì)調(diào)用TABLE_SHARE::clear_version()將m_version重置為0 |
從上面的過程我們可以知道:
TABLE對(duì)象已經(jīng)不僅僅是MySQL Server層的對(duì)象了,它是可以具體操作某一個(gè)存儲(chǔ)引擎的對(duì)象,所以TABLE對(duì)象作為MySQL層和存儲(chǔ)引擎之間的橋梁,可以直接使用TABLE對(duì)象的存儲(chǔ)引擎的句柄執(zhí)行各個(gè)公共C++ API來操作具體執(zhí)行動(dòng)作. |
總結(jié):
通過對(duì)源碼的淺析,我們已經(jīng)基本了解了MySQL Server層怎樣通過一步步獲得不同存儲(chǔ)引擎表結(jié)構(gòu)的信息,希望對(duì)大家理解MySQL Server層對(duì)象的緩存有一定的幫助.