两个 group by 语句都用了 order by null ,为什么使用内存临时表(使用memeory引擎)得到的语句结果里, 0 这个值在最后一行;而使用磁盘临时表(innodb引擎)得到的结果里, 0 这个值在第一行?
临时表和内存表这两个概念可是完全不同的。
假设有以下的两张表 t1 和 t2 ,其中表 t1 使用 Memory引擎, 表 t2 使用 InnoDB 引擎。
create table t1(id int primary key, c int) engine=Memory;
create table t2(id int primary key, c int) engine=innodb;
insert into t1 values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(0,0);
insert into t2 values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(0,0);
分别执行 select * from t1 和 select * from t2 。
可以看到,内存表 t1 的返回结果里面 0 在最后一行,而 InnoDB 表 t2 的返回结果里 0 在第一行。出现这个区别的原因,要从这两个引擎的主键索引的组织方式说起。
InnoDB 表的数据就放在主键索引树上,主键索引是 B+ 树。主键索引上的值是有序存储的。在执行 select * 的时候,就会按照叶子节点从左到右扫描,所以得到的结果里, 0 就出现在第一行。
与 InnoDB 引擎不同, Memory 引擎的数据和索引是分开的。我们来看一下表 t1 中的数据内容
内存表的数据部分以数组的方式单独存放,而主键 id 索引里,存的是每个数据的位置。主键 id 是 hash 索引,可以看到索引上的 key 并不是有序的。
在内存表 t1 中,当我执行 select * 的时候,走的是全表扫描,也就是顺序扫描这个数组。所以,0最后一个被读到
从中我们可以看出,这两个引擎的一些典型不同:
1. InnoDB 表的数据总是有序存放的,而内存表的数据就是按照写入顺序存放的;
2. 当数据文件有空洞的时候, InnoDB 表在插入新数据的时候,为了保证数据有序性,只能在固定的位置写入新值,而内存表找到空位就可以插入新值;
3. 数据位置发生变化的时候, InnoDB 表只需要修改主键索引,而内存表需要修改所有索引;
4. InnoDB 表用主键索引查询时需要走一次索引查找,用普通索引查询的时候,需要走两次索引查找。而内存表没有这个区别,所有索引的 “ 地位 ” 都是相同的。
5. InnoDB 支持变长数据类型,不同记录的长度可能不同;内存表不支持 Blob 和 Text 字段,并且即使定义了 varchar(N) ,实际也当作 char(N) ,也就是固定长度字符串来存储,因此内存表的每行数据长度相同。
由于内存表的这些特性,每个数据行被删除以后,空出的这个位置都可以被接下来要插入的数据复用:
delete from t1 where id=5;
insert into t1 values(10,10);
select * from t1
看到返回结果里, id=10 这一行出现在 id=4 之后,也就是原来 id=5 这行数据的位置。
由于表 t1 的这个主键索引是哈希索引,因此如果执行范围查询,比如select * from t1 where id<5;是用不上主键索引的,需要走全表扫描。(因为不是有序的,所以哈希索引做区间查询的速度是很慢)
内存表的优势是速度快,其中的一个原因就是 Memory 引擎支持hash 索引。当然,更重要的原因是,内存表的所有数据都保存在内存,而内存的读写速度总是比磁盘快。
这里的原因主要包括两个方面:
1. 锁粒度问题;
2. 数据持久化问题。
内存表不支持行锁,只支持表锁。因此,一张表只要有更新,就会堵住其他所有在这个表上的读写操作。
需要注意的是,这里的表锁跟之前我们介绍过的 MDL 锁不同,但都是表级的锁
在这个执行序列里, session A 的 update 语句要执行 50 秒,在这个语句执行期间 session B 的查询会进入锁等待状态:跟行锁比起来,表锁对并发访问的支持不够好
数据库重启的时候,所有的内存表都会被清空。
M-S 架构下,使用内存表存在的问题:
我们来看一下下面这个时序:
1. 业务正常访问主库;
2. 备库硬件升级,备库重启,内存表 t1 内容被清空;
3. 备库重启后,客户端发送一条 update 语句,修改表 t1 的数据行,这时备库应用线程就会报错 “ 找不到要更新的行 ” 。
这样就会导致主备同步停止。如果这时候发生主备切换的话,客户端会看到,表 t1 的数据 “ 丢失 ” 了。
由于 MySQL 知道重启之后,内存表的数据会丢失。所以,担心主库重启之后,出现主备不一致, MySQL 在实现上做了这样一
件事儿:在数据库重启之后,往 binlog 里面写入一行 DELETE FROM t1 。
如果使用所示的双 M 结构的话:
在备库重启的时候,备库 binlog 里的 delete 语句就会传到主库,然后把主库内存表的内容删除。使用的时候就会发现,主库的内存表数据突然被清空了
所以:建议你把普通内存表都用 InnoDB 表来代替。但是有一个例外:将innodb临时表可以改为内存临时表 (使用memory引擎的临时表)
一定要区分临时表和内存表这两个概念!!下面我们说的是用户自己创建的临时表,就是外部临时表
create temporary table temp_t(id int primary key, a int, b int, index(b))engine=innodb;
insert into temp_t select * from t2 where b>=1 and b<=2000;
select * from t1 join temp_t on (t1.b=temp_t.b);
改为内存临时表
create temporary table temp_t(id int primary key, a int, b int, index (b))engine=memory;
insert into temp_t select * from t2 where b>=1 and b<=2000;
select * from t1 join temp_t on (t1.b=temp_t.b);
内存临时表刚好可以无视内存表的两个不足:
1. 临时表不会被其他线程访问,没有并发性的问题;
2. 临时表重启后也是需要删除的,清空数据这个问题不存在;
3. 备库的临时表也不会影响主库的用户线程。
且内存临时表:使用内存表不需写磁盘,且使用hash索引查询速度块