【转】MySQL Temporary Table相关问题的探究

本文转载自: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'

这是丁奇提出的引导性的问题,几条语句看似简单,不过接下来我们提出的一连串问题与进
行的研究可都是围绕它们来的!

看到以上语句,你很容易会产生类似于以下的疑问:

1. 上述语句在一个session中先后创建了两个名为’tmp’的table,只不过一个是temporary
table,一个是normal table。问题来了:temporary table为何可以与同名的normal table
共存?

2. 上述语句成功执行了两条DROP TABLE语句,那么每一条语句操作的对象是哪个table呢?
亦即同名的temporary table与normal table之间的优先级关系是如何的?

很好,有了问题就知道了前进的方向!接下来我们就从这两个问题入手,由浅入深,开始我
们的探索之旅吧!

单机模式下的同名问题与优先级问题的探究

我们不妨从现象入手,先来验证第二个问题的结果究竟如何,即哪个表拥有较高的优先级?
为此我们设计如下的语句:

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)

以上语句做的工作很简单:先创建一个名为’tmp’的temporary table,并insert一个值;
之后创建一个名为’tmp’的normal table,也insert一个值。最终select时发现,两次
insert操作均作用于temporary table。

至此我们可以得到初步的印象是,同名的temporary table与normal table共存时,
temporary table较高的优先级。但是别忘了还存在另一种情况:先创建的表总有着较
高的优先级。这个猜想是很容易来验证它的对错的,我们只需将刚才的创建表的顺序调
换一下即可。这里就不再重复代码,直接给出结果:即使temporary table在normal table
之后创建,诸如select,insert,update等操作仍然优先作用于temporary table之上。
于是我们可以进一步猜测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

没问题吧!到这里我们已经从现象得出了初步的结论:在同一个session下同名的
temporary table与normal table共存时,temporary table总是优先被操作的。那么
我们可以更进一步提问:为什么temporary table的优先级会高于normal table呢?
而且别忘了在本段开始时我们还提出了一个问题:为什么在同一session下同名的
temporary table与normaltable可以共存?众所周知两个同名的temporary table或
normal table都是不被允许的。我们可以先做出猜想:temporary table与normal table
是存储在不同的位置的。这个猜想对吗?要回答这些问题,我们必须到MySQL的源码中
一探究竟,找寻答案了!

(我插几句:作为一个不折不扣的MySQL菜鸟,刚拿到MySQL源码时我就像拿到了天书,
除了膜拜之外根本不知道从何入手。经过一段时间的摸爬滚打,我高兴的发现我终于
窥得了其中的端倪,并深感“任务驱动+gdb”是上手的好方法。MySQL完整源码可以从
以下地址下载:http://dev.mysql.com/downloads/)

我们可以从创建一张表的流程入手,来探究这个过程(以下代码中,如果没有特别注明,
其注释均为原码注释。)

对于语句

create temporary table tmp(id int, data char(20));
create table tmp(id int, data char(20));

定位到./sql/sql_parse.cc中的mysql_execute_command()函数。

  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()函数的实现:

...

      /*
        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()函数实质上调用了./sql/sql_base.cc文件中的
find_table_in_list()函数。这个函数的功能就是去内存中的全局table list中遍历,
确认是否已有同名的normal table存在。注意,对于temporary table,到这里为止是不做
重名检查的。

继续跟踪到./sql/sql_talbe.cc中的mysql_create_table()函数。
开头的注释说的很清楚:

/*
  Database and name-locking aware wrapper for mysql_create_table_no_lock(),
*/

这个函数实际上是对mysql_create_table_no_lock()的一个封装,并且处理了一些加锁
机制。我们继续跟踪到同文件的mysql_create_table_no_lock()函数。

...

      /* 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);
  }

...

这里我们看到了一个关键函数build_tmptable_filename(),它位于./sql/sql_table.cc文件
中,这个函数是为temporary table命名的。在该函数内部我们又看到如下一段关键代码:

...

  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的命名规则就非常清楚了,其中current_pid为16进制
形式,thd->thread_id是Client的线程序号,thd->tmp_table就是临时表序号了,而reg_ext
就是形如*.frm这样的后缀。

现在我们回到函数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))
  {
    // 如果找到重名的表,那么执行这里的错误处理代码(非原注释)
  }

...

在上面这段代码中我们又看到了一个关键函数find_temporary_table(),这个函数内部是大
有文章的,它会去tmp_table list中去遍历并检查temporary table是否已经存在。如果一切
没有问题,那么继续往下执行:

...

  if (rea_create_table(thd, path, db, table_name,
                       create_info, alter_info->create_list,
                       key_count, key_info_buffer, file))

...

这里我们可以看到rea_create_table()函数的功能是创建normal table的实际数据文件。

...

  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()函数打开一个
temporary table并将其加入thd->temporary_table队列。继续往下,在函数末尾看到一
句代码:

  error= write_create_table_bin_log(thd, create_info, internal_tmp_table);

进入write_create_table_bin_log()函数,上来就是一段非常清晰的注释:

  /*
    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.
   */

已经说得很明白了,如果是内部创建的temporary table或者Row-based binlog模式下
创建temporary table或者binlog功能未开启,那么不写binlog,其他情况下都会写。

至此,MySQL一个典型的创建表的流程就走完了。总结上述代码,我们可以回答第一个问题,
也就是同名normal table与temporary table共存问题。现在我们知道,normal table与
temporary table保存的位置是不同的,temporary table保存在thd->temporary_table队列
中,而normal table是保存在全局的队列中的,这样同名的normal table与temporary table
就可以共存。并且,temporary table是相对于session的,因为session结束后相应的线程就
被回收了,那么对应于该线程的temporary table也就被释放了。更进一步,从temporary
table的命名规则我们可以看到,每个temporary table都对应着独特的客户端线程id,那么
显然各个Client之间同名的temporary table是允许共存的。而normal table显然是在任何情
况下都不允许同。

为了回答第二个问题,即优先级问题,我们只需要看一下drop一个表的过程即可,其他操作
的原理也是类似的。这里我们就不再像刚才那么详细的一步步分析源码,直接给出关键代码
(位于函数mysql_rm_table_part2()中,该函数位于./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); // 这里删除表的内容和索引(非原注释)
...
	/* 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更高的优先权。

好了,到目前为止我们已经从本质上回答了文章开头提出的两个问题,这样看起来问题已经
解决的比较圆满了。但是且慢,我们以上所做的探究全部基于同一台服务器下,如果是分布
式的系统,即主从模式下,又会出现什么样的状况呢?下面一节我们继续探究。

主从模式下temporary table机制的探究

首先我们要说明的是MySQL主从备份的实现机制。我们知道MySQL的众多日志类型中有一种为
binlog日志类型,凡是涉及到修改数据库的操作都会被记录到binlog日志中。binlog日志本
身又分为两种记录方式:Statement-based方式,Row-based方式(Mixed方式可以视为这两种
方式的混合)。在主从模式下,某个特定的分布式服务器群中有两种服务器:Master(主服务
器)与Slave(从服务器)。Master方将自己的数据修改痕迹以某种方式记录在本机的binlog文
件中,当有Slave连接到Master时,Master会启动Binlog dump线程来将本地的binlog内容发
送给Slave方。此时Slave方会启动两个线程:Slave I/O线程和Slave SQL线程。Slave I/O
线程读取从Master的Binlog dump线程发送过来的binlog内容,并将其写入本机的Relay log
中。Slave SQL线程则从本地的Relay log读取并且执行需要更新的事件。更具体的实现与配
置细节可以参考官方文档:http://dev.mysql.com/doc/refman/5.1/en/replication.html

注意到Slave方执行事件的线程只有一个,那就是Slave SQL线程。想一想按照我们目前的理
解,会出现怎样的问题?回忆刚才的MySQL temporary table命名规则,其中有一项是线程
id。再回忆刚才我们说到,由于temporary table是相对于session的,于是不同的Client可
以创建同名的temporary table。问题来了:将这个情景移到主从模式下,Master方同时连
接了两个Client,每一个Client各自创建了一个名为a的temporary table。我们假设此时
Master的binlog模式被设置为Statement-based,那么这两个建表事件都会被写入binlog。
现在Slave I/O线程检测并读取了这两个事件,Slave SQL线程要执行这两个事件了。按照
我们的想法,此时Slave是不能区分这两个temporary table的,因为线程id相同!

但是经过实际验证,MySQL能处理这个问题,而并没有像我们预想的那样会报错。那么MySQL
内部是如何处理的呢?让我们再仔细读一下建表函数mysql_create_table_no_lock()中的检
查temporary table名字冲突的函数find_temporary_table()的实现代码。

...

  key_length= create_table_def_key(thd, key, table_list, 1);

...

显然create_table_def_key()函数是区分每个temporary table的关键,我们继续看这个函数
内部的细节:

...

    int4store(key + key_length + 4, thd->variables.pseudo_thread_id);

...

这里我们看到一个关键信息:thd->variables.pseudo_thread_id。如果使用gdb调试,我们发
现在find_temporary_table()函数中thd->variables.pseudo_thread_id的值等于Relay-log中
的线程id,也就是Master的binlog中记录Client的线程id的值。然而注意到Slave SQL线程初
始化函数handle_slave_sql()中调用的 init_slave_thread()函数中有这样一句代码:

...

  thd->thread_id= thd->variables.pseudo_thread_id= thread_id++;

...

在这里,thd->variable.pseudo_thread_id是被初始化为Slave当前线程id的。那么它是何时被
修改的呢?继续看代码:

...

  while (!sql_slave_killed(thd,rli))
  {
    ...

    if (exec_relay_log_event(thd,rli))
    {

      ...

    }
  }

...

以上代码进入了执行relay log的循环。exec_relay_log_event()中调用了函数
apply_event_and_update_pos(),而这个函数中调用了ev->apply_event(),最终调用了
Query_log_event::do_apply_event()。在该函数中我们看到:

...

    thd->variables.pseudo_thread_id= thread_id;  // for temp tables

...

就是在这里,thd->variables.pseudo_thread_id已经被置为我们想要看到的值了。很神奇吧!

主从模式下temporary table可能造成的不同步问题

现在我们来考虑另外一个问题,即主从模式下temporary table可能引起的主从间不同步问
题。

回忆MySQL创建temporary table过程。该过程除了将temporary table信息加入当前线程所
拥有的temporary table队列之外,还做了一项工作,即在/tmp目录下创建了临时数据文件,
如:

#sql64d6_18_0.frm  #sql64d6_18_0.ibd (InnoDB下)

考虑以下情形:Master机上创建了一个temporary table,并且此时binlog模式为
Statement-based。于是Slave上读到了这个事件,并且在Slave上也同步了这个操作,即同样
建立了一个temporary table。此时由于某种原因,Slave突然意外重启。我们知道服务器
重启会导致所有/tmp文件夹下的数据文件被清空,那么在Slave上,原先的temporary table
不复存在。但是此时Master上的原始的temporary table还是好好的!这样,如果我们在
Master上做任何对该temporary table上的修改操作都会引起Slave端报错,产生类似以下信息:

Error 'Table 'test.tmp' doesn't exist' on query. Default database: 'test'.
Query: 'insert into tmp values(SomeValue)'

我们知道在Slave Server关闭后直到重启前,/tmp目录下的数据文件都是存在的。问题的本质
在于:Slave Server关闭后,内存中的temporary table链表被回收,导致/tmp下的数据文件
没有对应的数据结构,那么我们也就无从知晓对应的创建该表的Client到底是哪一个。

解决这个问题的基本思路就是在Slave重启时以某种方式恢复原先内存中的相关信息。其中一种
思路是,在Slave创建temporary table时,我们额外写一个文件来记录与维护数据文件与客户
端线程id、表名、数据库名的对应关系。另外一种思路是,在Slave创建temporary table时,
我们将相应的binlog记录下来,然后在启动的时候重做这些记录。具体的实现这里就不再详细
展开。

玩的开心!

时间: 2024-07-29 23:09:14

【转】MySQL Temporary Table相关问题的探究的相关文章

【转载】MySQL Temporary Table 相关问题的探究

[问题的引入]       让我们先来观察几条非常简单的 MySQL 语句: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 mysql> create temporary table tmp(id int, data char(20)); Query OK, 0 rows affected (0.01 sec)   mysql> create table tmp(id int, data char(20)); Query OK, 0 rows affected (

[MySQL 源码] MySQL drop table(压缩表)效率与流程分析

 之前发生过一起连续drop压缩表,最后长时间等待信号量crash,线上alert log里的报错是: OS WAIT ARRAY INFO: reservation count 36647199, signal count 34050225 --Thread 1331538240 has waited at row0purge.c line 680 for 950.00 seconds the semaphore: S-lock on RW-latch at 0xe60b60 '&dict_o

mysql alter table命令修改表结构实例详解_php实例

mysql alter table语句可以修改表的基本结构,例如添加字段.删除字段.添加主键.添加索引.修改字段数据类型.对表重命名等等操作,本文章通过两个简单的实例向大家介绍mysql alter table的使用方法.  实例一:使用ALTER TABLE命令向表中添加字段.修改字段类型以及设置主键. 首先创建一个表,SQL语句如下: mysql> CREATE TABLE myTable( -> ID SMALLINT -> ); 使用desc命令查看表结构: mysql>

mysql中可以相关删除吗?或者替代方案是什么

问题描述 mysql中可以相关删除吗?或者替代方案是什么 解决方案 级联删除?解决方案二:可以ALTER TABLE `test1`ADD CONSTRAINT `FK_test1_t1` FOREIGN KEY (`id`) REFERENCES `t1` (`id`) ON UPDATE CASCADE ON DELETE CASCADE;ON DELETE CASCADE解决方案三:一般会用事务来做,比如你删一个主表的一条记录,同时还想把与这个表有关系的表的数据也删掉的话 可以将这2个删除

mysql alter table命令修改表结构实例_Mysql

mysql实例之使用alter table命令修改表结构 mysql alter table语句可以修改表的基本结构,例如添加字段.删除字段.添加主键.添加索引.修改字段数据类型.对表重命名等等操作,本文章通过两个简单的实例向大家介绍mysql alter table的使用方法  实例一:使用ALTER TABLE命令向表中添加字段.修改字段类型以及设置主键. 首先创建一个表,SQL语句如下: mysql> CREATE TABLE myTable( -> ID SMALLINT ->

Mysql数据库主从复制相关介绍

公司使用master-slave架构,就具体学习了下,这里记录下相关内容. Replication的好处 读写分离:主数据库负责写和update数据,从数据库负责读操作 数据安全:由于数据复制到从库,并且可以对从库的复制进程进行停止操作,所以可以在从库上进行备份服务而不需要对主服务器进行中断操作 可分析:在线数据可以在master数据库上创建,然后在slave数据库上进行分析,且不会对master数据库造成性能影响 长数据:如果一个分支机构需要一份主要数据的copy,可以使用复制来创建一份本地备

mysql alter table 修改表命令详细介绍_Mysql

MySQL ALTER语法如下: ALTER [IGNORE] TABLE tbl_name alter_spec [, alter_spec ...] alter_specification: ADD [COLUMN] create_definition [FIRST | AFTER column_name ] or ADD INDEX [index_name] (index_col_name,...) or ADD PRIMARY KEY (index_col_name,...) or AD

mysql alter table修改表命令整理_Mysql

MYSQL ALTER TABLE命令用于修改表结构,例如添加/修改/删除字段.索引.主键等等,本文章通过实例向大家介绍MYSQL ALTER TABLE语句的使用方法,  MySQL ALTER语法如下: ALTER [IGNORE] TABLE tbl_name alter_spec [, alter_spec ...] alter_specification: ADD [COLUMN] create_definition [FIRST | AFTER column_name ] or AD

关于MYSQL flush table的作用

关于MYSQL flush table的作用 水平有限,还待学习.如有错误,请指正. 先给出官方文档: ? FLUSH TABLES Closes all open tables, forces all tables in use to be closed, and flushes the query cache. FLUSH TABLES also removes all query results from the query cache, like the RESET QUERY CACH