思考:两个group by语句都用了order bynull, 为什么使用内存临时表得到的语句结果里, 0这个值在最后一行; 而使用磁盘临时表得到的结果里, 0这个值在第一行?
答:答案对应第一小节:内存表的数据组织结构。
内存表的数据组织结构
假设有以下两张表t1、t2,其中表t1是Memory引擎,表t2是InnoDB引擎。
-- 创建表t1,t2,分别使用Memory引擎和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 t1values(1,1),(2,2),(3,3),(4,4),(5,5),(6,6),(7,7),(8,8),(9,9),(0,0);
insert into t2values(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在第一行。
表t2是InnoDB表,其数据就放在主键索引树上,主键索引是B+树。数据组织方式如下:
主键索引上的值是有序存储的,在执行select *的时候, 就会按照叶子节点从左到右扫描, 所以得到的结果里, 0就出现在第一行。
表t1是Memory表,而Memory表的数据和索引是分开的。数据组织方式如下:
从图中可以看到,内存表的数据部分以数组的方式单独存放,而主键id索引里,存的是每个数据的位置。主键id是hash索引,可以看到索引上的key并不是有序的。
在对表t1执行select *的时候, 走的是全表扫描, 也就是顺序扫描这个数组。 因此, 0就是最后一个被读到, 并放入结果集的数据。
问1:什么是索引组织表?什么是堆组织表?
- InnoDB引擎把数据放在主键索引上, 其他索引上保存的是主键id。 这种方式, 我们称之为索引组织表(IndexOrganizied Table) 。
- 而Memory引擎采用的是把数据单独存放, 索引上保存数据位置的数据组织形式, 我们称之为堆组织表(Heap Organizied Table) 。
问2:InnoDB和Memory引擎有哪些区别?
- InnoDB表的数据总是有序存放的, 而内存表的数据就是按照写入顺序存放的;
- 当数据文件有空洞的时候, InnoDB表在插入新数据的时候, 为了保证数据有序性, 只能在固定的位置写入新值, 而内存表找到空位就可以插入新值。
- 数据位置发生变化的时候, InnoDB表只需要修改主键索引, 而内存表需要修改所有索引。
- InnoDB表用主键索引查询时需要走一次索引查找, 用普通索引查询的时候, 需要走两次索引查找。 而内存表没有这个区别, 所有索引的“地位”都是相同的。
- InnoDB支持变长数据类型, 不同记录的长度可能不同; 内存表不支持Blob 和 Text字段, 并且即使定义了varchar(N), 实际也当作char(N), 也就是固定长度字符串来存储, 因此内存表的每行数据长度相同。
注:因为Memory表的主键索引是哈希索引,因此如果执行范围查询,只能走全表扫描。
hash索引和B-Tree索引
内存表也支持B-Tree索引。
在id列上创建B-Tree索引,语法如下:
alter table t1 add index a_btree_index using btree (id);
此时,表t1的数据组织形式如下:
问1:为什么内存表速度快?
- 内存表的所有数据都保存在内存中,而内存的读写速度总是比磁盘块。
- Memory引擎支持hash索引。
问2:既然内存表速度快,为什么生产环境不建议使用内存表?
- 锁粒度问题。内存表不支持行锁,只支持表锁,对并发访问不友好。一张表的更新,会阻塞所有在这个表上的读写操作。
- 数据持久化问题。数据库重启的时候,所有的内存表都会被清空,可能导致主从数据不一致。
内存表的锁
内存表不支持行锁,只支持表锁。
因此,如果一张表有更新,就会堵住其它所有在这个表上的读写操作。
模拟内存表的表级锁,示例:
在这个执行序列里, session A的update语句要执行50秒, 在这个语句执行期间session B的查询会进入锁等待状态。 session C的show processlist 结果输出如下:
跟行锁比起来, 表锁对并发访问的支持不够好。 所以, 内存表的锁粒度问题, 决定了它在处理并发事务的时候, 性能也不会太好。
数据持久性问题
如果数据库重启,所有的内存表都会被清空。
问:M-S架构下,使用内存表存在什么问题?
M-S基本架构如下:
看一下下面这个时序:
- 业务正常访问主库。
- 备库硬件升级, 备库重启, 内存表t1内容被清空。
- 备库重启后, 客户端发送一条update语句, 修改表t1的数据行, 这时备库应用线程就会报错“找不到要更新的行”。
内存表可能导致主备不一致。
由于MySQL知道重启后, 内存表的数据会丢失。 所以, 担心主库重启之后, 出现主备不一致, MySQL在实现上做了这样一件事儿: 在数据库重启之后, 往binlog里面写入一行DELETE FROM t1。
双M架构如下:
在备库重启的时候, 备库binlog里的delete语句就会传到主库, 然后把主库内存表的内容删除。这样你在使用的时候就会发现, 主库的内存表数据突然被清空了。
结论:无论是M-S架构,还是双M架构,内存表都不适合在生产环境上作为普通数据表使用。
内存表的使用场景
内存表的使用场景:作为临时表使用
1)临时表不会被其他线程访问,没有并发性的问题。
2)临时表重启后也是需要删除的,清空数据这个问题不存在。
3)备库的临时表也不会影响主库的用户线程。
4)操作临时表都是单个session线程,无需考虑并发问题。
小结:思考题
思考:假设你刚刚接手的一个数据库上, 真的发现了一个内存表。 备库重启之后肯定是会导致备库的内存表数据被清空, 进而导致主备同步停止。 这时, 最好的做法是将它修改成InnoDB引擎表。假设主库暂时不能修改引擎, 你可以加上什么自动化逻辑, 来避免主备同步停止呢?
1)假设的是主库暂时不能修改引擎,则先把备库的内存表引擎先都改成InnoDB。对于每个内存表,执行如下语句,避免备库重启的时候,数据丢失的问题。
set sql_log_bin=off;
alter table tbl_name engine=innodb;
2)主库重启后,会往binlog里面写“delete from tbl_name”,这个命令传到备库,备库的同名表数据也会被清空。因此,就不会出现主备同步停止的问题。
3)如果主库异常重启,触发了HA,之前修改过引擎的备库变成了主库。而旧主库变成了新备库,在新备库上把所有的内存表(这时候表里没数据)都改成InnoDB表。
所以,如果我们不能直接修改主库上的表引擎,可以配置一个自动巡检的工具,在备库上发现内存表就把引擎改了。