mysql实战38 | 都说InnoDB好,那还要不要使用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 t1 和 select * from t2。


                                       图 1 两个查询结果 -0 的位置

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

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

表 t2 用的是 InnoDB 引擎,它的主键索引 id 的组织方式,你已经很熟悉了:InnoDB 表的数据就放在主键索引树上,主键索引是 B+ 树。所以表 t2 的数据组织方式如下图所示:

                                                        图 2 表 t2 的数据组织

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

与 InnoDB 引擎不同,Memory 引擎的数据和索引是分开的。我们来看一下表 t1 中的数据内容。


                                                    图 3 表 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 表用主键索引查询时需要走一次索引查找,用普通索引查询的时候,需要走两次索引查找。而内存表没有这个区别,所有索引的“地位”都是相同的。
  5. InnoDB 支持变长数据类型,不同记录的长度可能不同;内存表不支持 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;
复制代码

是用不上主键索引的,需要走全表扫描。你可以借此再回顾下第 4 篇文章的内容。那如果要让内存表支持范围扫描,应该怎么办呢 ?

hash 索引和 B-Tree 索引

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

alter table t1 add index a_btree_index using btree (id);
复制代码

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


                                          图 4 表 t1 的数据组织 -- 增加 B-Tree 索引

新增的这个 B-Tree 索引你看着就眼熟了,这跟 InnoDB 的 b+ 树索引组织形式类似。

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


                           图 5 使用 B-Tree 和 hash 索引查询返回结果对比

可以看到,执行 select * from t1 where id<5 的时候,优化器会选择 B-Tree 索引,所以返回结果是 0 到 4。 使用 force index 强行使用主键 id 这个索引,id=0 这一行就在结果集的最末尾了。

其实,一般在我们的印象中,内存表的优势是速度快,其中的一个原因就是 Memory 引擎支持 hash 索引。当然,更重要的原因是,内存表的所有数据都保存在内存,而内存的读写速度总是比磁盘快。

但是,接下来我要跟你说明,为什么我不建议你在生产环境上使用内存表。这里的原因主要包括两个方面:

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

内存表的锁

我们先来说说内存表的锁粒度问题。

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

需要注意的是,这里的表锁跟之前我们介绍过的 MDL 锁不同,但都是表级的锁。接下来,我通过下面这个场景,跟你模拟一下内存表的表级锁。


                                                          图 6 内存表的表锁 -- 复现步骤

在这个执行序列里,session A 的 update 语句要执行 50 秒,在这个语句执行期间 session B 的查询会进入锁等待状态。session C 的 show processlist 结果输出如下:


                                                         图 7 内存表的表锁 -- 结果

跟行锁比起来,表锁对并发访问的支持不够好。所以,内存表的锁粒度问题,决定了它在处理并发事务的时候,性能也不会太好。

数据持久性问题

接下来,我们再看看数据持久性的问题。

数据放在内存中,是内存表的优势,但也是一个劣势。因为,数据库重启的时候,所有的内存表都会被清空。

你可能会说,如果数据库异常重启,内存表被清空也就清空了,不会有什么问题啊。但是,在高可用架构下,内存表的这个特点简直可以当做 bug 来看待了。为什么这么说呢?

我们先看看 M-S 架构下,使用内存表存在的问题。


我们来看一下下面这个时序:

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

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

在图 8 中这种有 proxy 的架构里,大家默认主备切换的逻辑是由数据库系统自己维护的。这样对客户端来说,就是“网络断开,重连之后,发现内存表数据丢失了”。

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

但是,接下来内存表的这个特性就会让使用现象显得更“诡异”了。由于 MySQL 知道重启之后,内存表的数据会丢失。所以,担心主库重启之后,出现主备不一致,MySQL 在实现上做了这样一件事儿:在数据库重启之后,往 binlog 里面写入一行 DELETE FROM t1。

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


                                                                    图 9 双 M 结构

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

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

有同学会说,但是内存表执行速度快呀。这个问题,其实你可以这么分析:

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

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

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

这个场景就是,我们在第 35 和 36 篇说到的用户临时表。在数据量可控,不会耗费过多内存的情况下,你可以考虑使用内存表。

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

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

现在,我们回过头再看一下第 35 篇 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 行,占用的内存有限。

因此,你可以对第 35 篇文章的语句序列做一个改写,将临时表 t1 改成内存临时表,并且在字段 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);
复制代码


                                                                图 10 使用内存临时表的执行效果

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

小结

今天这篇文章,我从“要不要使用内存表”这个问题展开,和你介绍了 Memory 引擎的几个特性。

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


因此,在生产上,我不建议你使用普通内存表。

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

基于内存表的特性,我们还分析了它的一个适用场景,就是内存临时表。内存表支持 hash 索引,这个特性利用起来,对复杂查询的加速效果还是很不错的。

最后,我给你留一个问题吧。

假设你刚刚接手的一个数据库上,真的发现了一个内存表。备库重启之后肯定是会导致备库的内存表数据被清空,进而导致主备同步停止。这时,最好的做法是将它修改成 InnoDB 引擎表。

假设当时的业务场景暂时不允许你修改引擎,你可以加上什么自动化逻辑,来避免主备同步停止呢?

你可以把你的思考和分析写在评论区,我会在下一篇文章的末尾跟你讨论这个问题。感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一起阅读。

上期问题时间

今天文章的正文内容,已经回答了我们上期的问题,这里就不再赘述了。

转载于:https://juejin.im/post/5d05f6f2e51d45108223fc5a

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/274956.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计类的五个原则_内容设计的5个原则

设计类的五个原则重点 (Top highlight)There are many heuristics and principles for creating good content. Some are created from a UX perspective, others from a content marketing point of view. They range from very long to very concise ones. I reviewed a larg…

Umi 4 RC 发布

大家好&#xff0c;我是若川。感谢大家一年以来的支持和陪伴。这一年疫情反复&#xff0c;年底应该有由于疫情不能回家的小伙伴。在这里先祝福大家&#xff0c;新年快乐。本打算今天不发文&#xff0c;但看到这篇觉得不错&#xff0c;就发一下。大家好&#xff0c;Umi 4 经过几…

让你沉迷的五种设计

让你沉迷的五种设计 好游戏总是能令人沉迷其中无法自拔&#xff0c;外媒cracked经过分析&#xff0c;发现有五种设计方法必不可少&#xff1b; 1.斯金纳箱原理&#xff1a;这是行为心理学派在实验室内研究动物学习能力的箱形实验装置&#xff0c;游戏开发也得益于此&#xff0c…

Java学习路线详解

有很多的[Java请添加链接描述](http://www.hfxms.com.cn/java/)程序员&#xff0c;在初期学习时&#xff0c;通常会对如何学习而感到迷茫。[合肥学码思请添加链接描述](http://www.hfxms.com.cn/)小编就为大家分析如何学好Java编程&#xff0c;相信能帮助那些正在处于迷茫状态的…

figma下载_在Figma中将约束与布局网格一起使用

figma下载While doing research for the book “Designing in Figma”, I discovered a powerful way to lay out objects using a combination of Layout Grid and Constraints. The interface of Figma does not indicate a connection between the two, so it can be discov…

换一种方式表达

http://player.youku.com/player.php/sid/XMjY2MTE5NDU2/v.swf 转载于:https://www.cnblogs.com/JCSU/archive/2012/03/17/2403324.html

新的一年,碎片化学习前端,我推荐这几个公众号~

大家好&#xff0c;我是若川。假期余额不足&#xff0c;无法充值。快乐的时光总是短暂的。马上又开始一年的学习和“奋斗”。前端技术日新月异&#xff0c;发展迅速&#xff0c;作为一个与时俱进的前端工程师&#xff0c;需要不断的学习。这里强烈推荐几个前端开发工程师必备的…

Java单元测试之JUnit4详解

2019独角兽企业重金招聘Python工程师标准>>> Java单元测试之JUnit4详解 与JUnit3不同&#xff0c;JUnit4通过注解的方式来识别测试方法。目前支持的主要注解有&#xff1a; BeforeClass 全局只会执行一次&#xff0c;而且是第一个运行Before 在测试方法运行之前运行…

我在黑暗中看到你眼中的月光_你好黑暗,我的老朋友

我在黑暗中看到你眼中的月光(Originally published on https://web.dev/prefers-color-scheme/.)(最初发布于https://web.dev/prefers-color-scheme/ 。) 介绍 (Introduction) &#x1f4da; I have done a lot of background research on the history and theory of dark mod…

ant 实现批量打包android应用

很多的应用中需要加上应用推广的统计&#xff0c;如果一个一个的去生成不同渠道包的应用&#xff0c;效率低不说&#xff0c;还有可能不小心弄错了分发渠道&#xff0c;使用ant可以批量生成应用。一、添加渠道包信息为了统计渠道信息&#xff0c;就不得不在程序的某个地方加入渠…

Element Plus 正式版发布啦!

大家好&#xff0c;我是若川。祝大家新年快乐&#xff0c;开工大吉。公众号回复「红包」可以领取源码共读红包封面。持续组织了6个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时…

大型网站技术架构(一)大型网站架构演化

2019独角兽企业重金招聘Python工程师标准>>> 看完了有一本书&#xff0c;就应该有所收获&#xff0c;有所总结&#xff0c;最近把《大型网站技术架构》一书给看完了&#xff0c;给人的印象实在深刻&#xff0c;再加上之前也搞过书本上讲的反向代理和负载均衡以及ses…

永不示弱_永不过时的网页设计:今天和2000年的在线投资组合

永不示弱重点 (Top highlight)Philippe Starck, a renowned industrial designer, once said:著名的工业设计师Philippe Starck曾经说过&#xff1a; “A designer has a duty to create timeless design. To be timeless you have to think really far into the future, not …

如何使用 React 创建一个作品集网站

大家好&#xff0c;我是若川。持续组织了6个月源码共读活动&#xff0c;感兴趣的可以点此加我微信 ruochuan12 参与&#xff0c;每周大家一起学习200行左右的源码&#xff0c;共同进步。同时极力推荐订阅我写的《学习源码整体架构系列》 包含20余篇源码文章。今天&#xff0c;你…

博弈论入门 HDU 1850

Problem Description 一年在外 父母时刻牵挂春节回家 你能做几天好孩子吗寒假里尝试做做下面的事情吧陪妈妈逛一次菜场悄悄给爸爸买个小礼物主动地 强烈地 要求洗一次碗某一天早起 给爸妈用心地做回早餐如果愿意 你还可以和爸妈说咱们玩个小游戏吧 ACM课上学的呢&#xff5e;下…

新的一年,如何高效学习前端前沿知识~

今天强烈推荐一些互联网行业内优质技术公众号&#xff0c;互联网人大部分都关注了&#xff0c;包括目前主流的公司技术团队号&#xff0c;技术社区号&#xff0c;个人技术号&#xff0c;这些号行业深耕已久&#xff0c;会给你带来事半功倍的效果。公众号那么多&#xff0c;文章…

RabbitMQ学习总结(7)——Spring整合RabbitMQ实例

2019独角兽企业重金招聘Python工程师标准>>> 1.RabbitMQ简介 RabbitMQ是流行的开源消息队列系统&#xff0c;用erlang语言开发。RabbitMQ是AMQP&#xff08;高级消息队列协议&#xff09;的标准实现。 官网&#xff1a;http://www.rabbitmq.com/ 2.Spring集成Rabbi…

UIScrollView实现不全屏分页的小技巧

浏览一系列图片时&#xff0c;我们会经常使用UIScrollView来进行实现&#xff0c;因为其自带的pagingEnabled属性&#xff0c;可以非常方便的自动支持手势左右轻扫切换切换页面。pagingEnabled不过最近做的一个产品&#xff0c;设计的效果有所不同&#xff0c;希望每一页图片的…

谈谈对java中分层的理解_让我们谈谈网页设计中的卡片设计

谈谈对java中分层的理解“I want a card”, this is the first demand point that the customer said in the last issue when talking to me about demand. There is no doubt that the card type is excellent for both PC and mobile phones. From online shopping malls to…

1-jdk的安装与配置

1- Jvm、jdk、jre之间的关系 JVM&#xff1a;Java虚拟机&#xff0c;保证java程序跨平台。&#xff08;Java Virtual Machine&#xff09;JRE&#xff1a; Java运行环境&#xff0c;包含JVM和核心类库。如果只是想运行java程序&#xff0c;只要安装JRE即可。&#xff08;Java R…