Memory存储引擎

Hi,我是阿昌,今天学习记录的是关于Memory存储引擎的内容。

两个 group by 语句都用了 order by null,为什么使用内存临时表得到的语句结果里,0 这个值在最后一行;

而使用磁盘临时表得到的结果里,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 t1select * from t2

可以看到,内存表 t1 的返回结果里面 0 在最后一行,而 InnoDB 表 t2 的返回结果里 0 在第一行。

出现这个区别的原因,要从这两个引擎的主键索引的组织方式说起。

表 t2 用的是 InnoDB 引擎,它的主键索引 id 的组织方式:

InnoDB 表的数据就放在主键索引树上,主键索引是 B+ 树。

所以表 t2 的数据组织方式如下图所示:


主键索引上的值是有序存储的。在执行 select * 的时候,就会按照叶子节点从左到右扫描,所以得到的结果里,0 就出现在第一行。


与 InnoDB 引擎不同,Memory 引擎的数据和索引是分开的。

来看一下表 t1 中的数据内容。

可以看到,内存表的数据部分以数组的方式单独存放,而主键 id 索引里,存的是每个数据的位置。

主键 id 是 hash 索引,可以看到索引上的 key 并不是有序的。

在内存表 t1 中,当我执行 select * 的时候,走的是全表扫描,也就是顺序扫描这个数组。

因此,0 就是最后一个被读到,并放入结果集的数据。


可见,InnoDB 和 Memory 引擎的数据组织方式是不同的

- InnoDB 引擎把数据放在主键索引上,其他索引上保存的是主键 id。这种方式,称之为索引组织表(Index Organizied Table)。

  • Memory 引擎采用的是把数据单独存放,索引上保存数据位置的数据组织形式,称之为堆组织表(Heap Organizied Table)。

从中可以看出,这两个引擎的一些典型不同:

  1. InnoDB 表的数据总是有序存放的,而内存表的数据就是按照写入顺序存放的;
  2. 当数据文件有空洞的时候,InnoDB 表在插入新数据的时候,为了保证数据有序性,只能在固定的位置写入新值,而内存表找到空位就可以插入新值;
  3. 数据位置发生变化的时候,InnoDB 表只需要修改主键索引,而内存表需要修改所有索引;
  4. InnoDB 表用主键索引查询时需要走一次索引查找,用普通索引查询的时候,需要走两次索引查找。而内存表没有这个区别,所有索引的“地位”都是相同的。InnoDB 支持变长数据类型,不同记录的长度可能不同;
  5. 内存表不支持 Blob 和 Text 字段,并且即使定义了 varchar(N),实际也当作 char(N),也就是固定长度字符串来存储,因此内存表的每行数据长度相同。

由于内存表的这些特性,每个数据行被删除以后,空出的这个位置都可以被接下来要插入的数据复用

比如,如果要在表 t1 中执行:

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;

是用不上主键索引的,需要走全表扫描


二、hash 索引和 B-Tree 索引

那如果要让内存表支持范围扫描,应该怎么办呢 ?

实际上,内存表也是支持 B-Tree 索引的。在 id 列上创建一个 B-Tree 索引,SQL 语句可以这么写:

alter table t1 add index a_btree_index using btree (id);

这时,表 t1 的数据组织形式就变成了这样:

这跟 InnoDB 的 b+ 树索引组织形式类似。

作为对比,可以看一下这下面这两个语句的输出:

可以看到,执行 select * from t1 where id<5 的时候,优化器会选择 B-Tree 索引,所以返回结果是 0 到 4。

使用 force index 强行使用主键 id 这个索引,id=0 这一行就在结果集的最末尾了。

内存表的优势是速度快,其中的一个原因就是 Memory 引擎支持 hash 索引

当然,更重要的原因是,内存表的所有数据都保存在内存,而内存的读写速度总是比磁盘快。


为什么不建议在生产环境上使用内存表。这里的原因主要包括两个方面:

  1. 锁粒度问题;
  2. 数据持久化问题。

三、内存表的锁

我们先来说说内存表的锁粒度问题。内存表不支持行锁,只支持表锁

因此,一张表只要有更新,就会堵住其他所有在这个表上的读写操作。

需要注意的是,这里的表锁跟之前介绍过的 MDL 锁不同,但都是表级的锁。

模拟一下内存表的表级锁。

在这个执行序列里,session A 的 update 语句要执行 50 秒,在这个语句执行期间 session B 的查询会进入锁等待状态。

session C 的 show processlist 结果输出如下:

跟行锁比起来,表锁对并发访问的支持不够好。

所以,内存表的锁粒度问题,决定了它在处理并发事务的时候,性能也不会太好。


四、数据持久性问题

数据放在内存中,是内存表的优势,但也是一个劣势。

因为,数据库重启的时候,所有的内存表都会被清空。

如果数据库异常重启,内存表被清空也就清空了,不会有什么问题啊。

但是,在高可用架构下,内存表的这个特点简直可以当做 bug 来看待了。

为什么这么说呢?先看看 M-S 架构下,使用内存表存在的问题。


来看一下下面这个时序:

  1. 业务正常访问主库;
  2. 备库硬件升级,备库重启,内存表 t1 内容被清空;
  3. 备库重启后,客户端发送一条 update 语句,修改表 t1 的数据行,这时备库应用线程就会报错“找不到要更新的行”。

这样就会导致主备同步停止。当然,如果这时候发生主备切换的话,客户端会看到,表 t1 的数据“丢失”了。

在图 8 中这种有 proxy 的架构里,大家默认主备切换的逻辑是由数据库系统自己维护的。

这样对客户端来说,就是“网络断开,重连之后,发现内存表数据丢失了”。

可能说这还好啊,毕竟主备发生切换,连接会断开,业务端能够感知到异常。


但是,接下来内存表的这个特性就会让使用现象显得更“诡异”了。

由于 MySQL 知道重启之后,内存表的数据会丢失。

所以,担心主库重启之后,出现主备不一致,MySQL 在实现上做了这样一件事儿:

在数据库重启之后,往 binlog 里面写入一行 DELETE FROM t1

如果使用是如图 9 所示的双 M 结构的话:

在备库重启的时候,备库 binlog 里的 delete 语句就会传到主库,然后把主库内存表的内容删除。这样你在使用的时候就会发现,主库的内存表数据突然被清空了。

基于上面的分析,内存表并不适合在生产环境上作为普通数据表使用。


但是内存表执行速度快呀。

这个问题,其实可以这么分析:

  1. 如果你的表更新量大,那么并发度是一个很重要的参考指标,InnoDB 支持行锁,并发度比内存表好;能放到内存表的数据量都不大。
  2. 如果你考虑的是读的性能,一个读 QPS 很高并且数据量不大的表,即使是使用 InnoDB,数据也是都会缓存在 InnoDB Buffer Pool 里的。因此,使用 InnoDB 表的读性能也不会差。

所以,建议你把普通内存表都用 InnoDB 表来代替。

但是,有一个场景却是例外的。


在数据量可控,不会耗费过多内存的情况下,可以考虑使用内存表。

内存临时表刚好可以无视内存表的两个不足,主要是下面的三个原因:

  1. 临时表不会被其他线程访问,没有并发性的问题;
  2. 临时表重启后也是需要删除的,清空数据这个问题不存在;
  3. 备库的临时表也不会影响主库的用户线程。

再看一下Join语句优化的例子,当时建议的是创建一个 InnoDB 临时表,使用的语句序列是:

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);

这里使用内存临时表的效果更好,原因有三个:

  1. 相比于 InnoDB 表,使用内存表不需要写磁盘,往表 temp_t 的写数据的速度更快
  2. 索引 b 使用 hash 索引,查找的速度比 B-Tree 索引快;
  3. 临时表数据只有 2000 行,占用的内存有限

因此,将临时表 temp_t 改成内存临时表,并且在字段 b 上创建一个 hash 索引。

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);

可以看到,不论是导入数据的时间,还是执行 join 的时间,使用内存临时表的速度都比使用 InnoDB 临时表要更快一些。


五、总结

由于重启会丢数据,如果一个备库重启,会导致主备同步线程停止;如果主库跟这个备库是双 M 架构,还可能导致主库的内存表数据被删掉。

因此,在生产上,不建议你使用普通内存表。如果是 DBA,可以在建表的审核系统中增加这类规则,要求业务改用 InnoDB 表。InnoDB 表性能还不错,而且数据安全也有保障。而内存表由于不支持行锁,更新语句会阻塞查询,性能也未必就如想象中那么好。

基于内存表的特性,还分析了它的一个适用场景,就是内存临时表。

内存表支持 hash 索引,这个特性利用起来,对复杂查询的加速效果还是很不错的。


假设刚刚接手的一个数据库上,真的发现了一个内存表。
备库重启之后肯定是会导致备库的内存表数据被清空,进而导致主备同步停止。
这时,最好的做法是将它修改成 InnoDB 引擎表。
假设当时的业务场景暂时不允许修改引擎,可以加上什么自动化逻辑,来避免主备同步停止呢?

在备库上设置跳过主库内存表的同步,避免因为同步主库内存表的错误而影响了其他表的同步进度。


Day902.Memory存储引擎 -MySQL实战相关推荐

  1. mysql表jiayi_MySQL Memory存储引擎:优势及性能测试

    MySQL Memory存储引擎:优势及性能测试 上一篇 / 下一篇  2012-08-10 18:29:44 Memory存储引擎将表的数据存放在内存中.Memory替代以前的Heap成为首选项,但 ...

  2. mysql memory优点_MySQL Memory存储引擎:优势及性能测试

    [IT168技术]需求源自项目中的MemCache需求,开始想用MemCached(官方站点:http://memcached.org/ ),但这个在Linux下面应用广泛的开源软件无官方支持的Win ...

  3. MySQL Memory 存储引擎浅析

    MySQL Memory 存储引擎浅析 吴剑 2011-11-29 原创文章,转载必需注明出处:http://www.cnblogs.com/wu-jian/ 吴剑 http://www.cnblog ...

  4. 3种mysql的储存机制_MySQL三种InnoDB、MyISAM和MEMORY存储引擎对比

    三种引擎的区别: 事务:InnoDB支持事务,MyISAM和MEMORY两个不支持. 存储限制:InnoDB有64TB的存储限制,MyISAM和MEMORY要跟具体情况而定. 空间使用:InnoDB对 ...

  5. [存储引擎] MySQL的存储引擎介绍

    前言 数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建.查询.更新和删除数据.不同的存储引擎提供不同的存储机制.索引技巧.锁定水平等功能,使用不同的存储引擎,还可以 ...

  6. jdbc mysql查询显示图片_在实现JDBC时如何显示存储引擎-MySQL CONNECTION查询?

    使用SELECT ENGINE显示存储引擎名称.让我们首先创建一个表-create table DemoTable -> ( -> Id int NOT NULL AUTO_INCREME ...

  7. 实战干货|自研数据存储迁移MySQL实战

    背景   最近公司内部在做某自研数据存储的下线工作,这里我们暂且化名其为DistributeSQL,由于DistributeSQL不再进行服务支持,需要迁移项目中使用到该存储到其他数据存储中.   本 ...

  8. 数据库服务器不支持innodb存储引擎,MySQL InnoDB存储引擎表损坏恢复指南

    目的 故障排除步骤 适用于: MySQL服务器版本 5.0至5.7[5.0版到5.7] A 第一反应 A.1 停止,备份,重启 停止MySQL 服务器.如果它已经下线或崩溃,跳到步骤 2. 代码: / ...

  9. Memory存储引擎

    Memory使用内存空间创建:每个Memory表実际上都对应一个磁盘文件,用于持久化.Memory表因为数据是存放在内存中的,因此访问速度极快,通常使用Hash索引来実現数据索引. Memory表的缺 ...

最新文章

  1. C#中数值型数据保留N位小数方法
  2. Ceph 客户端的 RPM 包升级问题
  3. C#与.NET程序员面试宝典 1.3.1 网络应聘(图)
  4. Silverlight项目构成
  5. redis的持久化方式有哪些?
  6. how many libraries within Cambridge?
  7. 介绍“Razor”— ASP.NET的一个新视图引擎
  8. flowable实战(十一)flowable 删除数据库表先后顺序
  9. 数据结构-树2-二叉树各种函数实现
  10. sqlquerystress
  11. QT新建项目显示项目未配置的原因与解决方案
  12. 解决 Exchange 2013提示“出现意外错误,无法处理您的请求”,无法打开OWA和ECP...
  13. USB转TTL连接ESP01S
  14. 浅谈游戏被唾弃的原因
  15. rp文件chrome浏览器查看插件
  16. 鸢尾花数据集的各种玩法
  17. (2015秋) 团队博客及分组情况
  18. 仓库温度湿度控制措施_一般仓库的温湿度控制范围是多少合适?
  19. 服务器有哪些品牌,各品牌服务器之间性价比对比!
  20. 计算机一级补考53分能过吗,我考了53分

热门文章

  1. python 的高级特性:函数式编程,lambda表达式,装饰器
  2. VMware虚拟机设置 桥接模式:直接连接物理网络下的复制物理网络连接状态选项有什么作用?(续订ip)
  3. IM即时通讯-7-如何设计通知提醒
  4. C++move函数详解
  5. C语言空白符、空格符 与转义字符题点总结
  6. 弘玑RPA x 流程挖掘,构筑超自动化的坚实底座
  7. 五款免费的网络管理工具
  8. 计算机一级过的技巧,计算机一级考试内容及考试技巧
  9. js:一百以内质素的求法
  10. [ubuntu] 外挂硬盘