36-为什么临时表可以重名?
# 临时表的特性
“临时表就是内存表” 这是一种错误的认知。
- 内存表:值的是用 Memory 引擎创建的表,语法为
create table … engine=memory
。数据都在内存中,一旦重启数据丢失但是表结构还在。 - 临时表:可以使用各种引擎,如果是使用 InnoDB 或者 MyISAM 创建的临时表,数据是写到磁盘上的。
临时表有这些特点
- 建表语法是
create temporary table
。 - 一个临时表只能被创建它的 session 访问,对其他的线程不可见。
- 临时表可以与普通表同名。
- 一个 session 中有同名的临时表和普通表的时候,show create 语句,以及增删改查访问的都是临时表
- show tables 不会显示临时表。
- session 关闭临时表就会自动被删除。
- 临时表特别适合用在上一章说过的 join 语句中。
# 临时表的应用
分库分表是第一个典型的适用场景,分库分表就是把一个逻辑上的大表分散到不同的数据库实例上,比如一个表 ht,按照字段 f,拆分成 1024 个表,分部在 32 个数据库上。
一般情况下,这种分库分表中间都一个 proxy 层,用来计算到底改到那个分区去查询。
select v from ht where f=N;
如果查询语句如上所述,就很容易,直接用 N%1024 就能得到在哪个分区上。 但是如果没有用到分区字段 f,而是用了一个索引字段 k,执行语句如下。
select v from ht where k >= M order by t_modified desc limit 100;
这种就需要去所有的分区中查询满足条件的行。然后统一做 order by 操作。
这种情况下一般有两个解决方案
- 在 proxy 层中写代码进行手动排序,但是 sql 要是麻烦的话难度较大。
- 汇总所有的表中符合数据到临时表进行逻辑操作。
大概流程是
- 在一个汇总的库中创建一个临时表 temp_ht,包含三个字段 v、k、t_modified。
- 在每个分库中执行
select v,k,t_modified from ht_x where k >= M order by t_modified desc limit 100;
。 - 把结果放到 temp_ht 中。执行
select v from temp_ht order by t_modified desc limit 100;
。
# 临时表为什么可以重名?
在创建 InnoDB 表时会 MySQL 会给这个 InnoDB 表创建文件保存表结构定义,表数据存放、内存数据定义。
- 表结构定义:frm 的文件后缀为.frm,前缀为
#sql{进程 id}_{线程 id}_序列号
。所以线程 id 不会有重复。 - 数据:MySQL5.6 及之前的版本都是存在一个相同前缀但是以.ibd 结尾的后缀文件中。从 5.7 版本开始,MySQL 引入了一个临时表空间,存放数据,不会生成.ibd 文件。
- 内存:每个表都对应一个 table_def_key。普通表的值是 “库名 + 表名”。临时表在这个基础上加了 “server_id+thread_id”。
综上临时表可以重复,其实每个线程都维护了自己的临时表链表,每次 session 内操作表的时候,先遍历链表检查是否有自己的链表,如果有就从临时表操作,session 结束的时候执行删除操作。
# 临时表和主备复制
- binlog_format=statement/mixed 的时候,备库才会创建临时表,但是在主库 session 关闭的时候主库会自动删除临时表,而备库需要在手动在主库上写一个删除临时表的语句同步。
- 在 row 格式下,备库不会生成临时表。
正好解释一个问题,MySQL 在记录 binlog 的时候,不论是 create table 还是 alter table 语句,都是原样记录,甚至于连空格都不变。但是如果执行 drop table t_normal,系统记录 binlog 就会写成: DROP TABLE t_normal /* generated by server */
。
这是因为,当 binlog_format 设置为 row 时,binlog 记录的是每个操作影响的数据行(这也解释了上面说的为什么 row 不创建临时表了,因为临时表根部不会影响普通表的行),而不是 SQL 语句。因此,对于 DROP TABLE t_normal, temp_t 这个命令,binlog 只会记录删除 t_normal 表的操作,而不会记录删除 temp_t 表的操作。备库上根本不会创建 temp_t 表。
主库上不同的线程创建同名的临时表是没关系的,但是传到备库的同步线程会为每个从主库接收到的会话创建一个对应的会话,并在这个会话中执行操作。因此,即使这些操作中包含了创建同名的临时表,由于这些临时表在各自的会话中是隔离的,所以在备库上也不会产生冲突。
# 上一章答案
select * from t1 join t2 on(t1.a=t2.a) join t3 on (t2.b=t3.b) where t1.c>=X and t2.c>=Y and t3.c>=Z;
如果改写成 straight_join,要怎么指定连接顺序,以及怎么给三个表创建索引?
我自己的观点
- 如果按照 t1、t2、t3 的顺序,那么应该在 t2 的 a 和 t3 的 b 加索引,t2 的 c 和 t3 的 c 上加索引,因为 t1 是驱动表,必须要全表扫描,加索引的意义不大。
- 如果能确定 XYZ 的具体值的话,可以判断出哪个表在过滤后是小表,然后作为驱动表,然后第二小的作为第一项的被驱动表,因为两者作为一个整体会当做驱动表被全表扫描。然后还是在对应的 a、b、c 上加索引。