一、内存表的数据组织结构:
对于表
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);
1、查询时:
分别执行select * from t1;
和select * from t2;
,结果为:
注意:内存表t1的返回结果里面0在最后一行,而InnoDB表t2的返回结果里0在第一行。
2、两个引擎的主键索引的组织方式:
<1>、InnoDB:
InnoDB表的数据就放在主键索引树上,主键索引是B+树。
主键索引上的值是有序存储的。在执行select *的时候,就会按照叶子节点从左到右扫描,所以得到的结果里0就出现在第一行。
<2>、Memory:
Memory引擎的数据和索引是分开的。内存表的数据部分以数组的方式单独存放,而主键id索引里,存的是每个数据的位置。主键id是hash索引,索引上的key并不是有序的。执行select *的时候,走的是全表扫描,也就是顺序扫描这个数组。因此,0就是最后一个被读到,并放入结果集的数据。
3、InnoDB和Memory引擎的数据组织方式:
- InnoDB引擎把数据放在主键索引上,其他索引上保存的是主键id。这种方式称之为索引组织表(Index Organizied Table)。
- Memory引擎采用的是把数据单独存放,索引上保存数据位置。这种方式称之为堆组织表(Heap Organizied Table)。
4、InnoDB和Memory的异同点:
InnoDB表的数据总是有序存放的,而内存表的数据就是按照写入顺序存放的;
当数据文件有空洞的时候,InnoDB表在插入新数据的时候,为了保证数据有序性,只能在固定的位置写入新值,而内存表找到空位就可以插入新值;
数据位置发生变化的时候,InnoDB表只需要修改主键索引,而内存表需要修改所有索引;
InnoDB表用主键索引查询时需要走一次索引查找,用普通索引查询的时候,需要走两次索引查找。而内存表没有这个区别,所有索引的“地位”都是相同的。
InnoDB支持变长数据类型,不同记录的长度可能不同;内存表不支持Blob 和 Text字段,并且即使定义了varchar(N),实际也当作char(N),也就是固定长度字符串来存储,因此内存表的每行数据长度相同。
二、hash索引和B-Tree索引:
1、内存表也支持B-Tree索引:
在id列上创建一个B-Tree索引,SQL语句可以这么写:alter table t1 add index a_btree_index using btree (id);
此时表t1的数据组织形式变为:
此时的select输出为:
注意:执行select * from t1 where id<5的时候,优化器会选择B-Tree索引,所以返回结果是0到4。 使用force index强行使用主键id这个索引,id=0这一行就在结果集的最末尾了。
三、不建议在生产环境上使用内存表的原因:
1、锁粒度问题:
<1>、内存表不支持行锁,只支持表锁。因此,一张表只要有更新,就会堵住其他所有在这个表上的读写操作。
<2>、对于线程:
在这个执行序列里,session A的update语句要执行50秒,在这个语句执行期间session B的查询会进入锁等待状态。session C的show processlist 结果输出如下:2、数据持久化问题:
<1>、M-S架构下,使用内存表存在的问题:
对于下面这个时序:
- 业务正常访问主库;
- 备库硬件升级,备库重启,内存表t1内容被清空;
- 备库重启后,客户端发送一条update语句,修改表t1的数据行,这时备库应用线程就会报错“找不到要更新的行”。
- 这样就会导致主备同步停止。当然,如果这时候发生主备切换的话,客户端会看到,表t1的数据“丢失”了。
在有proxy的架构里,默认主备切换的逻辑是由数据库系统自己维护的。这样对客户端来说,就是“网络断开,重连之后,发现内存表数据丢失了”。
<1>、双M架构下,使用内存表存在的问题:
由于MySQL知道重启之后,内存表的数据会丢失。所以,担心主库重启之后,出现主备不一致,MySQL在实现上做了这样一件事儿:在数据库重启之后,往binlog里面写入一行DELETE FROM t1;
。
在备库重启的时候,备库binlog里的delete语句就会传到主库,然后把主库内存表的内容删除。这样在使用的时候就会发现,主库的内存表数据突然被清空了。