mysql global temporary table_【转】MySQL Temporary Table相关问题的探究
本文轉(zhuǎn)載自:http://itindex.net/detail/10901-mysql-temporary-table
問題的引入
讓我們先來觀察幾條非常簡單的MySQL語句:
mysql> create temporary table tmp(id int, data char(20));
Query OK, 0 rows affected (0.00 sec)
mysql> create table tmp(id int, data char(20));
Query OK, 0 rows affected (0.00 sec)
mysql> drop table tmp;
Query OK, 0 rows affected (0.00 sec)
mysql> drop table tmp;
Query OK, 0 rows affected (0.00 sec)
mysql> drop table tmp;
ERROR 1051 (42S02): Unknown table 'tmp'
這是丁奇提出的引導(dǎo)性的問題,幾條語句看似簡單,不過接下來我們提出的一連串問題與進(jìn)
行的研究可都是圍繞它們來的!
看到以上語句,你很容易會產(chǎn)生類似于以下的疑問:
1. 上述語句在一個session中先后創(chuàng)建了兩個名為’tmp’的table,只不過一個是temporary
table,一個是normal table。問題來了:temporary table為何可以與同名的normal table
共存?
2. 上述語句成功執(zhí)行了兩條DROP TABLE語句,那么每一條語句操作的對象是哪個table呢?
亦即同名的temporary table與normal table之間的優(yōu)先級關(guān)系是如何的?
很好,有了問題就知道了前進(jìn)的方向!接下來我們就從這兩個問題入手,由淺入深,開始我
們的探索之旅吧!
單機(jī)模式下的同名問題與優(yōu)先級問題的探究
我們不妨從現(xiàn)象入手,先來驗證第二個問題的結(jié)果究竟如何,即哪個表擁有較高的優(yōu)先級?
為此我們設(shè)計如下的語句:
mysql> create temporary table tmp(id1 int, data1 char(20));
Query OK, 0 rows affected (0.00 sec)
mysql> describe tmp;
+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id1 | int(11) | YES | | NULL | |
| data1 | char(20) | YES | | NULL | |
+-------+----------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> insert into tmp values(1, "Some");
Query OK, 1 row affected (0.00 sec)
mysql> select * from tmp;
+------+-------+
| id1 | data1 |
+------+-------+
| 1 | Some |
+------+-------+
1 row in set (0.00 sec)
mysql> create table tmp(id2 int, data2 char(20));
Query OK, 0 rows affected (0.00 sec)
mysql> describe tmp;
+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id1 | int(11) | YES | | NULL | |
| data1 | char(20) | YES | | NULL | |
+-------+----------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> insert into tmp values(2, "Some");
Query OK, 1 row affected (0.00 sec)
mysql> select * from tmp;
+------+-------+
| id1 | data1 |
+------+-------+
| 1 | Some |
| 2 | Some |
+------+-------+
2 rows in set (0.00 sec)
以上語句做的工作很簡單:先創(chuàng)建一個名為’tmp’的temporary table,并insert一個值;
之后創(chuàng)建一個名為’tmp’的normal table,也insert一個值。最終select時發(fā)現(xiàn),兩次
insert操作均作用于temporary table。
至此我們可以得到初步的印象是,同名的temporary table與normal table共存時,
temporary table較高的優(yōu)先級。但是別忘了還存在另一種情況:先創(chuàng)建的表總有著較
高的優(yōu)先級。這個猜想是很容易來驗證它的對錯的,我們只需將剛才的創(chuàng)建表的順序調(diào)
換一下即可。這里就不再重復(fù)代碼,直接給出結(jié)果:即使temporary table在normal table
之后創(chuàng)建,諸如select,insert,update等操作仍然優(yōu)先作用于temporary table之上。
于是我們可以進(jìn)一步猜測drop表的時候,先drop的也是temporary table。
馬上來驗證一下:
/* 緊接著之前的代碼 */
mysql> drop table tmp;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from tmp;
Empty set (0.01 sec)
mysql> describe tmp;
+-------+----------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+----------+------+-----+---------+-------+
| id2 | int(11) | YES | | NULL | |
| data2 | char(20) | YES | | NULL | |
+-------+----------+------+-----+---------+-------+
2 rows in set (0.00 sec)
mysql> drop table tmp;
Query OK, 0 rows affected (0.00 sec)
mysql> show tables;
Empty set (0.00 sec)
mysql> describe tmp;
ERROR 1146 (42S02): Table 'test.tmp' doesn't exist
沒問題吧!到這里我們已經(jīng)從現(xiàn)象得出了初步的結(jié)論:在同一個session下同名的
temporary table與normal table共存時,temporary table總是優(yōu)先被操作的。那么
我們可以更進(jìn)一步提問:為什么temporary table的優(yōu)先級會高于normal table呢?
而且別忘了在本段開始時我們還提出了一個問題:為什么在同一session下同名的
temporary table與normaltable可以共存?眾所周知兩個同名的temporary table或
normal table都是不被允許的。我們可以先做出猜想:temporary table與normal table
是存儲在不同的位置的。這個猜想對嗎?要回答這些問題,我們必須到MySQL的源碼中
一探究竟,找尋答案了!
(我插幾句:作為一個不折不扣的MySQL菜鳥,剛拿到MySQL源碼時我就像拿到了天書,
除了膜拜之外根本不知道從何入手。經(jīng)過一段時間的摸爬滾打,我高興的發(fā)現(xiàn)我終于
窺得了其中的端倪,并深感“任務(wù)驅(qū)動+gdb”是上手的好方法。MySQL完整源碼可以從
以下地址下載:http://dev.mysql.com/downloads/)
我們可以從創(chuàng)建一張表的流程入手,來探究這個過程(以下代碼中,如果沒有特別注明,
其注釋均為原碼注釋。)
對于語句
create temporary table tmp(id int, data char(20));
create table tmp(id int, data char(20));
定位到./sql/sql_parse.cc中的mysql_execute_command()函數(shù)。
case SQLCOM_CREATE_TABLE:
{
...
if ((res= create_table_precheck(thd, select_tables, create_table)))
goto end_with_restore_list;
...
/* regular create */
if (create_info.options & HA_LEX_CREATE_TABLE_LIKE)
res= mysql_create_like_table(thd, create_table, select_tables,
&create_info);
else
{
res= mysql_create_table(thd, create_table->db,
create_table->table_name, &create_info,
&alter_info, 0, 0);
}
...
}
首先我們查看同文件中create_table_precheck()函數(shù)的實現(xiàn):
...
/*
For temporary tables we don't have to check if the created table exists
*/
if (!(lex->create_info.options & HA_LEX_CREATE_TMP_TABLE) &&
find_table_in_global_list(tables, create_table->db,
create_table->table_name))
{
error= FALSE;
goto err;
}
...
而find_table_in_global_list()函數(shù)實質(zhì)上調(diào)用了./sql/sql_base.cc文件中的
find_table_in_list()函數(shù)。這個函數(shù)的功能就是去內(nèi)存中的全局table list中遍歷,
確認(rèn)是否已有同名的normal table存在。注意,對于temporary table,到這里為止是不做
重名檢查的。
繼續(xù)跟蹤到./sql/sql_talbe.cc中的mysql_create_table()函數(shù)。
開頭的注釋說的很清楚:
/*
Database and name-locking aware wrapper for mysql_create_table_no_lock(),
*/
這個函數(shù)實際上是對mysql_create_table_no_lock()的一個封裝,并且處理了一些加鎖
機(jī)制。我們繼續(xù)跟蹤到同文件的mysql_create_table_no_lock()函數(shù)。
...
/* Check if table exists */
if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
{
path_length= build_tmptable_filename(thd, path, sizeof(path));
create_info->table_options|=HA_CREATE_DELAY_KEY_WRITE;
}
else
{
path_length= build_table_filename(path, sizeof(path) - 1, db, alias, reg_ext,
internal_tmp_table ? FN_IS_TMP : 0);
}
...
這里我們看到了一個關(guān)鍵函數(shù)build_tmptable_filename(),它位于./sql/sql_table.cc文件
中,這個函數(shù)是為temporary table命名的。在該函數(shù)內(nèi)部我們又看到如下一段關(guān)鍵代碼:
...
my_snprintf(p, bufflen - (p - buff), "/%s%lx_%lx_%x%s",
tmp_file_prefix, current_pid,
thd->thread_id, thd->tmp_table++, reg_ext);
...
有了以上這段代碼,temporary table的命名規(guī)則就非常清楚了,其中current_pid為16進(jìn)制
形式,thd->thread_id是Client的線程序號,thd->tmp_table就是臨時表序號了,而reg_ext
就是形如*.frm這樣的后綴。
現(xiàn)在我們回到函數(shù)mysql_create_table_no_lock(),緊接著剛才的代碼:
/* Check if table already exists */
if ((create_info->options & HA_LEX_CREATE_TMP_TABLE) &&
find_temporary_table(thd, db, table_name))
{
// 如果找到重名的表,那么執(zhí)行這里的錯誤處理代碼(非原注釋)
}
...
在上面這段代碼中我們又看到了一個關(guān)鍵函數(shù)find_temporary_table(),這個函數(shù)內(nèi)部是大
有文章的,它會去tmp_table list中去遍歷并檢查temporary table是否已經(jīng)存在。如果一切
沒有問題,那么繼續(xù)往下執(zhí)行:
...
if (rea_create_table(thd, path, db, table_name,
create_info, alter_info->create_list,
key_count, key_info_buffer, file))
...
這里我們可以看到rea_create_table()函數(shù)的功能是創(chuàng)建normal table的實際數(shù)據(jù)文件。
...
if (create_info->options & HA_LEX_CREATE_TMP_TABLE)
{
/* Open table and put in temporary table list */
if (!(open_temporary_table(thd, path, db, table_name, 1)))
{
(void) rm_temporary_table(create_info->db_type, path);
goto unlock_and_end;
}
thd->thread_specific_used= TRUE;
}
...
上面這段代碼是對temporary table操作的,其中open_temporary_table()函數(shù)打開一個
temporary table并將其加入thd->temporary_table隊列。繼續(xù)往下,在函數(shù)末尾看到一
句代碼:
error= write_create_table_bin_log(thd, create_info, internal_tmp_table);
進(jìn)入write_create_table_bin_log()函數(shù),上來就是一段非常清晰的注釋:
/*
Don't write statement if:
- It is an internal temporary table,
- Row-based logging is used and it we are creating a temporary table, or
- The binary log is not open.
Otherwise, the statement shall be binlogged.
*/
已經(jīng)說得很明白了,如果是內(nèi)部創(chuàng)建的temporary table或者Row-based binlog模式下
創(chuàng)建temporary table或者binlog功能未開啟,那么不寫binlog,其他情況下都會寫。
至此,MySQL一個典型的創(chuàng)建表的流程就走完了。總結(jié)上述代碼,我們可以回答第一個問題,
也就是同名normal table與temporary table共存問題。現(xiàn)在我們知道,normal table與
temporary table保存的位置是不同的,temporary table保存在thd->temporary_table隊列
中,而normal table是保存在全局的隊列中的,這樣同名的normal table與temporary table
就可以共存。并且,temporary table是相對于session的,因為session結(jié)束后相應(yīng)的線程就
被回收了,那么對應(yīng)于該線程的temporary table也就被釋放了。更進(jìn)一步,從temporary
table的命名規(guī)則我們可以看到,每個temporary table都對應(yīng)著獨特的客戶端線程id,那么
顯然各個Client之間同名的temporary table是允許共存的。而normal table顯然是在任何情
況下都不允許同。
為了回答第二個問題,即優(yōu)先級問題,我們只需要看一下drop一個表的過程即可,其他操作
的原理也是類似的。這里我們就不再像剛才那么詳細(xì)的一步步分析源碼,直接給出關(guān)鍵代碼
(位于函數(shù)mysql_rm_table_part2()中,該函數(shù)位于./sql/sql_table.cc)
...
error= drop_temporary_table(thd, table); // 這里刪除臨時表(非原注釋)
...
error= ha_delete_table(thd, table_type, path, db, table->table_name,
!dont_log_query); // 這里刪除表的內(nèi)容和索引(非原注釋)
...
/* Delete the table definition file */
strmov(end,reg_ext);
// 以下刪除表的定義文件(非原注釋)
if (!(new_error=my_delete(path,MYF(MY_WME))))
{
some_tables_deleted=1;
new_error= Table_triggers_list::drop_all_triggers(thd, db,
table->table_name);
}
...
從以上代碼我們不難看出,drop表的過程總是先走temporary table,再走normal table的。
這也就解釋了為何temporary table有著比normal table更高的優(yōu)先權(quán)。
好了,到目前為止我們已經(jīng)從本質(zhì)上回答了文章開頭提出的兩個問題,這樣看起來問題已經(jīng)
解決的比較圓滿了。但是且慢,我們以上所做的探究全部基于同一臺服務(wù)器下,如果是分布
式的系統(tǒng),即主從模式下,又會出現(xiàn)什么樣的狀況呢?下面一節(jié)我們繼續(xù)探究。
主從模式下temporary table機(jī)制的探究
首先我們要說明的是MySQL主從備份的實現(xiàn)機(jī)制。我們知道MySQL的眾多日志類型中有一種為
binlog日志類型,凡是涉及到修改數(shù)據(jù)庫的操作都會被記錄到binlog日志中。binlog日志本
身又分為兩種記錄方式:Statement-based方式,Row-based方式(Mixed方式可以視為這兩種
方式的混合)。在主從模式下,某個特定的分布式服務(wù)器群中有兩種服務(wù)器:Master(主服務(wù)
器)與Slave(從服務(wù)器)。Master方將自己的數(shù)據(jù)修改痕跡以某種方式記錄在本機(jī)的binlog文
件中,當(dāng)有Slave連接到Master時,Master會啟動Binlog dump線程來將本地的binlog內(nèi)容發(fā)
送給Slave方。此時Slave方會啟動兩個線程:Slave I/O線程和Slave SQL線程。Slave I/O
線程讀取從Master的Binlog dump線程發(fā)送過來的binlog內(nèi)容,并將其寫入本機(jī)的Relay log
中。Slave SQL線程則從本地的Relay log讀取并且執(zhí)行需要更新的事件。更具體的實現(xiàn)與配
置細(xì)節(jié)可以參考官方文檔:http://dev.mysql.com/doc/refman/5.1/en/replication.html
注意到Slave方執(zhí)行事件的線程只有一個,那就是Slave SQL線程。想一想按照我們目前的理
解,會出現(xiàn)怎樣的問題?回憶剛才的MySQL temporary table命名規(guī)則,其中有一項是線程
id。再回憶剛才我們說到,由于temporary table是相對于session的,于是不同的Client可
以創(chuàng)建同名的temporary table。問題來了:將這個情景移到主從模式下,Master方同時連
接了兩個Client,每一個Client各自創(chuàng)建了一個名為a的temporary table。我們假設(shè)此時
Master的binlog模式被設(shè)置為Statement-based,那么這兩個建表事件都會被寫入binlog。
現(xiàn)在Slave I/O線程檢測并讀取了這兩個事件,Slave SQL線程要執(zhí)行這兩個事件了。按照
我們的想法,此時Slave是不能區(qū)分這兩個temporary table的,因為線程id相同!
但是經(jīng)過實際驗證,MySQL能處理這個問題,而并沒有像我們預(yù)想的那樣會報錯。那么MySQL
內(nèi)部是如何處理的呢?讓我們再仔細(xì)讀一下建表函數(shù)mysql_create_table_no_lock()中的檢
查temporary table名字沖突的函數(shù)find_temporary_table()的實現(xiàn)代碼。
...
key_length= create_table_def_key(thd, key, table_list, 1);
...
顯然create_table_def_key()函數(shù)是區(qū)分每個temporary table的關(guān)鍵,我們繼續(xù)看這個函數(shù)
內(nèi)部的細(xì)節(jié):
...
int4store(key + key_length + 4, thd->variables.pseudo_thread_id);
...
這里我們看到一個關(guān)鍵信息:thd->variables.pseudo_thread_id。如果使用gdb調(diào)試,我們發(fā)
現(xiàn)在find_temporary_table()函數(shù)中thd->variables.pseudo_thread_id的值等于Relay-log中
的線程id,也就是Master的binlog中記錄Client的線程id的值。然而注意到Slave SQL線程初
始化函數(shù)handle_slave_sql()中調(diào)用的 init_slave_thread()函數(shù)中有這樣一句代碼:
...
thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;
...
在這里,thd->variable.pseudo_thread_id是被初始化為Slave當(dāng)前線程id的。那么它是何時被
修改的呢?繼續(xù)看代碼:
...
while (!sql_slave_killed(thd,rli))
{
...
if (exec_relay_log_event(thd,rli))
{
...
}
}
...
以上代碼進(jìn)入了執(zhí)行relay log的循環(huán)。exec_relay_log_event()中調(diào)用了函數(shù)
apply_event_and_update_pos(),而這個函數(shù)中調(diào)用了ev->apply_event(),最終調(diào)用了
Query_log_event::do_apply_event()。在該函數(shù)中我們看到:
...
thd->variables.pseudo_thread_id= thread_id; // for temp tables
...
就是在這里,thd->variables.pseudo_thread_id已經(jīng)被置為我們想要看到的值了。很神奇吧!
主從模式下temporary table可能造成的不同步問題
現(xiàn)在我們來考慮另外一個問題,即主從模式下temporary table可能引起的主從間不同步問
題。
回憶MySQL創(chuàng)建temporary table過程。該過程除了將temporary table信息加入當(dāng)前線程所
擁有的temporary table隊列之外,還做了一項工作,即在/tmp目錄下創(chuàng)建了臨時數(shù)據(jù)文件,
如:
#sql64d6_18_0.frm #sql64d6_18_0.ibd (InnoDB下)
考慮以下情形:Master機(jī)上創(chuàng)建了一個temporary table,并且此時binlog模式為
Statement-based。于是Slave上讀到了這個事件,并且在Slave上也同步了這個操作,即同樣
建立了一個temporary table。此時由于某種原因,Slave突然意外重啟。我們知道服務(wù)器
重啟會導(dǎo)致所有/tmp文件夾下的數(shù)據(jù)文件被清空,那么在Slave上,原先的temporary table
不復(fù)存在。但是此時Master上的原始的temporary table還是好好的!這樣,如果我們在
Master上做任何對該temporary table上的修改操作都會引起Slave端報錯,產(chǎn)生類似以下信息:
Error 'Table 'test.tmp' doesn't exist' on query. Default database: 'test'.
Query: 'insert into tmp values(SomeValue)'
我們知道在Slave Server關(guān)閉后直到重啟前,/tmp目錄下的數(shù)據(jù)文件都是存在的。問題的本質(zhì)
在于:Slave Server關(guān)閉后,內(nèi)存中的temporary table鏈表被回收,導(dǎo)致/tmp下的數(shù)據(jù)文件
沒有對應(yīng)的數(shù)據(jù)結(jié)構(gòu),那么我們也就無從知曉對應(yīng)的創(chuàng)建該表的Client到底是哪一個。
解決這個問題的基本思路就是在Slave重啟時以某種方式恢復(fù)原先內(nèi)存中的相關(guān)信息。其中一種
思路是,在Slave創(chuàng)建temporary table時,我們額外寫一個文件來記錄與維護(hù)數(shù)據(jù)文件與客戶
端線程id、表名、數(shù)據(jù)庫名的對應(yīng)關(guān)系。另外一種思路是,在Slave創(chuàng)建temporary table時,
我們將相應(yīng)的binlog記錄下來,然后在啟動的時候重做這些記錄。具體的實現(xiàn)這里就不再詳細(xì)
展開。
玩的開心!
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的mysql global temporary table_【转】MySQL Temporary Table相关问题的探究的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 蛀牙会引起口臭吗?
- 下一篇: 安装mysql删除_mysql安装和删除