learn.lianglianglee.com/专栏/MySQL实战45讲/38 都说InnoDB好,那还要不要使用Memory引擎?.md.html
2022-08-14 03:40:33 +08:00

431 lines
29 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>38 都说InnoDB好那还要不要使用Memory引擎.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/MySQL实战45讲/00 开篇词 这一次让我们一起来搞懂MySQL.md.html">00 开篇词 这一次让我们一起来搞懂MySQL</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/01 基础架构一条SQL查询语句是如何执行的.md.html">01 基础架构一条SQL查询语句是如何执行的</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/02 日志系统一条SQL更新语句是如何执行的.md.html">02 日志系统一条SQL更新语句是如何执行的</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/03 事务隔离:为什么你改了我还看不见?.md.html">03 事务隔离:为什么你改了我还看不见?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/04 深入浅出索引(上).md.html">04 深入浅出索引(上)</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/05 深入浅出索引(下).md.html">05 深入浅出索引(下)</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/06 全局锁和表锁 :给表加个字段怎么有这么多阻碍?.md.html">06 全局锁和表锁 :给表加个字段怎么有这么多阻碍?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/07 行锁功过:怎么减少行锁对性能的影响?.md.html">07 行锁功过:怎么减少行锁对性能的影响?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/08 事务到底是隔离的还是不隔离的?.md.html">08 事务到底是隔离的还是不隔离的?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/09 普通索引和唯一索引,应该怎么选择?.md.html">09 普通索引和唯一索引,应该怎么选择?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/10 MySQL为什么有时候会选错索引.md.html">10 MySQL为什么有时候会选错索引</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/11 怎么给字符串字段加索引?.md.html">11 怎么给字符串字段加索引?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/12 为什么我的MySQL会“抖”一下.md.html">12 为什么我的MySQL会“抖”一下</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/13 为什么表数据删掉一半,表文件大小不变?.md.html">13 为什么表数据删掉一半,表文件大小不变?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/14 count()这么慢,我该怎么办?.md.html">14 count()这么慢,我该怎么办?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/15 答疑文章(一):日志和索引相关问题.md.html">15 答疑文章(一):日志和索引相关问题</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/16 “order by”是怎么工作的.md.html">16 “order by”是怎么工作的</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/17 如何正确地显示随机消息?.md.html">17 如何正确地显示随机消息?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/18 为什么这些SQL语句逻辑相同性能却差异巨大.md.html">18 为什么这些SQL语句逻辑相同性能却差异巨大</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/19 为什么我只查一行的语句,也执行这么慢?.md.html">19 为什么我只查一行的语句,也执行这么慢?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/20 幻读是什么,幻读有什么问题?.md.html">20 幻读是什么,幻读有什么问题?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/21 为什么我只改一行的语句,锁这么多?.md.html">21 为什么我只改一行的语句,锁这么多?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/22 MySQL有哪些“饮鸩止渴”提高性能的方法.md.html">22 MySQL有哪些“饮鸩止渴”提高性能的方法</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/23 MySQL是怎么保证数据不丢的.md.html">23 MySQL是怎么保证数据不丢的</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/24 MySQL是怎么保证主备一致的.md.html">24 MySQL是怎么保证主备一致的</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/25 MySQL是怎么保证高可用的.md.html">25 MySQL是怎么保证高可用的</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/26 备库为什么会延迟好几个小时?.md.html">26 备库为什么会延迟好几个小时?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/27 主库出问题了,从库怎么办?.md.html">27 主库出问题了,从库怎么办?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/28 读写分离有哪些坑?.md.html">28 读写分离有哪些坑?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/29 如何判断一个数据库是不是出问题了?.md.html">29 如何判断一个数据库是不是出问题了?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/30 答疑文章(二):用动态的观点看加锁.md.html">30 答疑文章(二):用动态的观点看加锁</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/31 误删数据后除了跑路,还能怎么办?.md.html">31 误删数据后除了跑路,还能怎么办?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/32 为什么还有kill不掉的语句.md.html">32 为什么还有kill不掉的语句</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/33 我查这么多数据,会不会把数据库内存打爆?.md.html">33 我查这么多数据,会不会把数据库内存打爆?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/34 到底可不可以使用join.md.html">34 到底可不可以使用join</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/35 join语句怎么优化.md.html">35 join语句怎么优化</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/36 为什么临时表可以重名?.md.html">36 为什么临时表可以重名?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/37 什么时候会使用内部临时表?.md.html">37 什么时候会使用内部临时表?</a>
</li>
<li>
<a class="current-tab" href="/专栏/MySQL实战45讲/38 都说InnoDB好那还要不要使用Memory引擎.md.html">38 都说InnoDB好那还要不要使用Memory引擎</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/39 自增主键为什么不是连续的?.md.html">39 自增主键为什么不是连续的?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/40 insert语句的锁为什么这么多.md.html">40 insert语句的锁为什么这么多</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/41 怎么最快地复制一张表?.md.html">41 怎么最快地复制一张表?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/42 grant之后要跟着flush privileges吗.md.html">42 grant之后要跟着flush privileges吗</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/43 要不要使用分区表?.md.html">43 要不要使用分区表?</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/44 答疑文章(三):说一说这些好问题.md.html">44 答疑文章(三):说一说这些好问题</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/45 自增id用完怎么办.md.html">45 自增id用完怎么办</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/我的MySQL心路历程.md.html">我的MySQL心路历程</a>
</li>
<li>
<a href="/专栏/MySQL实战45讲/结束语 点线网面一起构建MySQL知识网络.md.html">结束语 点线网面一起构建MySQL知识网络</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>38 都说InnoDB好那还要不要使用Memory引擎</h1>
<p>我在上一篇文章末尾留给你的问题是:两个 group by 语句都用了 order by null为什么使用内存临时表得到的语句结果里0 这个值在最后一行而使用磁盘临时表得到的结果里0 这个值在第一行?</p>
<p>今天我们就来看看,出现这个问题的原因吧。</p>
<h1>内存表的数据组织结构</h1>
<p>为了便于分析,我来把这个问题简化一下,假设有以下的两张表 t1 和 t2其中表 t1 使用 Memory 引擎, 表 t2 使用 InnoDB 引擎。</p>
<pre><code>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);
</code></pre>
<p>然后,我分别执行 select * from t1 和 select * from t2。</p>
<p><img src="assets/3fb1100b6e3390357d4efff0ba4765e6.png" alt="img" /></p>
<p>图 1 两个查询结果 -0 的位置</p>
<p>可以看到,内存表 t1 的返回结果里面 0 在最后一行,而 InnoDB 表 t2 的返回结果里 0 在第一行。</p>
<p>出现这个区别的原因,要从这两个引擎的主键索引的组织方式说起。</p>
<p>表 t2 用的是 InnoDB 引擎,它的主键索引 id 的组织方式你已经很熟悉了InnoDB 表的数据就放在主键索引树上,主键索引是 B+ 树。所以表 t2 的数据组织方式如下图所示:</p>
<p><img src="assets/4e29e4f9db55ace6ab09161c68ad8c8d.jpg" alt="img" /></p>
<p>图 2 表 t2 的数据组织</p>
<p>主键索引上的值是有序存储的。在执行 select * 的时候就会按照叶子节点从左到右扫描所以得到的结果里0 就出现在第一行。</p>
<p>与 InnoDB 引擎不同Memory 引擎的数据和索引是分开的。我们来看一下表 t1 中的数据内容。</p>
<p><img src="assets/dde03e92074cecba4154d30cd16a9684.jpg" alt="img" /></p>
<p>图 3 表 t1 的数据组织</p>
<p>可以看到,内存表的数据部分以数组的方式单独存放,而主键 id 索引里,存的是每个数据的位置。主键 id 是 hash 索引,可以看到索引上的 key 并不是有序的。</p>
<p>在内存表 t1 中,当我执行 select * 的时候走的是全表扫描也就是顺序扫描这个数组。因此0 就是最后一个被读到,并放入结果集的数据。</p>
<p>可见InnoDB 和 Memory 引擎的数据组织方式是不同的:</p>
<ul>
<li>InnoDB 引擎把数据放在主键索引上,其他索引上保存的是主键 id。这种方式我们称之为<strong>索引组织表</strong>Index Organizied Table</li>
<li>而 Memory 引擎采用的是把数据单独存放,索引上保存数据位置的数据组织形式,我们称之为<strong>堆组织表</strong>Heap Organizied Table</li>
</ul>
<p>从中我们可以看出,这两个引擎的一些典型不同:</p>
<ol>
<li>InnoDB 表的数据总是有序存放的,而内存表的数据就是按照写入顺序存放的;</li>
<li>当数据文件有空洞的时候InnoDB 表在插入新数据的时候,为了保证数据有序性,只能在固定的位置写入新值,而内存表找到空位就可以插入新值;</li>
<li>数据位置发生变化的时候InnoDB 表只需要修改主键索引,而内存表需要修改所有索引;</li>
<li>InnoDB 表用主键索引查询时需要走一次索引查找,用普通索引查询的时候,需要走两次索引查找。而内存表没有这个区别,所有索引的“地位”都是相同的。</li>
<li>InnoDB 支持变长数据类型,不同记录的长度可能不同;内存表不支持 Blob 和 Text 字段,并且即使定义了 varchar(N),实际也当作 char(N),也就是固定长度字符串来存储,因此内存表的每行数据长度相同。</li>
</ol>
<p>由于内存表的这些特性,每个数据行被删除以后,空出的这个位置都可以被接下来要插入的数据复用。比如,如果要在表 t1 中执行:</p>
<pre><code>delete from t1 where id=5;
insert into t1 values(10,10);
select * from t1;
</code></pre>
<p>就会看到返回结果里id=10 这一行出现在 id=4 之后,也就是原来 id=5 这行数据的位置。</p>
<p>需要指出的是,表 t1 的这个主键索引是哈希索引,因此如果执行范围查询,比如</p>
<pre><code>select * from t1 where id&lt;5;
</code></pre>
<p>是用不上主键索引的,需要走全表扫描。你可以借此再回顾下[第 4 篇文章]的内容。那如果要让内存表支持范围扫描,应该怎么办呢 </p>
<h1>hash 索引和 B-Tree 索引</h1>
<p>实际上,内存表也是支 B-Tree 索引的。在 id 列上创建一个 B-Tree 索引SQL 语句可以这么写:</p>
<pre><code>alter table t1 add index a_btree_index using btree (id);
</code></pre>
<p>这时,表 t1 的数据组织形式就变成了这样:</p>
<p><img src="assets/1788deca56cb83c114d8353c92e3bde3.jpg" alt="img" /></p>
<p>图 4 表 t1 的数据组织 -- 增加 B-Tree 索引</p>
<p>新增的这个 B-Tree 索引你看着就眼熟了,这跟 InnoDB 的 b+ 树索引组织形式类似。</p>
<p>作为对比,你可以看一下这下面这两个语句的输出:</p>
<p><img src="assets/a85808fcccab24911d257d720550328a.png" alt="img" /></p>
<p>图 5 使用 B-Tree 和 hash 索引查询返回结果对比</p>
<p>可以看到,执行 select * from t1 where id&lt;5 的时候,优化器会选择 B-Tree 索引,所以返回结果是 0 到 4。 使用 force index 强行使用主键 id 这个索引id=0 这一行就在结果集的最末尾了。</p>
<p>其实,一般在我们的印象中,内存表的优势是速度快,其中的一个原因就是 Memory 引擎支持 hash 索引。当然,更重要的原因是,内存表的所有数据都保存在内存,而内存的读写速度总是比磁盘快。</p>
<p>但是,接下来我要跟你说明,为什么我不建议你在生产环境上使用内存表。这里的原因主要包括两个方面:</p>
<ol>
<li>锁粒度问题;</li>
<li>数据持久化问题。</li>
</ol>
<h1>内存表的锁</h1>
<p>我们先来说说内存表的锁粒度问题。</p>
<p>内存表不支持行锁,只支持表锁。因此,一张表只要有更新,就会堵住其他所有在这个表上的读写操作。</p>
<p>需要注意的是,这里的表锁跟之前我们介绍过的 MDL 锁不同,但都是表级的锁。接下来,我通过下面这个场景,跟你模拟一下内存表的表级锁。</p>
<p><img src="assets/f216e2d707559ed2ca98fbe21e509f29.png" alt="img" /></p>
<p>图 6 内存表的表锁 -- 复现步骤</p>
<p>在这个执行序列里session A 的 update 语句要执行 50 秒,在这个语句执行期间 session B 的查询会进入锁等待状态。session C 的 show processlist 结果输出如下:</p>
<p><img src="assets/14d88076dad6db573f0b66f2c17df916.png" alt="img" /></p>
<p>图 7 内存表的表锁 -- 结果</p>
<p>跟行锁比起来,表锁对并发访问的支持不够好。所以,内存表的锁粒度问题,决定了它在处理并发事务的时候,性能也不会太好。</p>
<h1>数据持久性问题</h1>
<p>接下来,我们再看看数据持久性的问题。</p>
<p>数据放在内存中,是内存表的优势,但也是一个劣势。因为,数据库重启的时候,所有的内存表都会被清空。</p>
<p>你可能会说,如果数据库异常重启,内存表被清空也就清空了,不会有什么问题啊。但是,在高可用架构下,内存表的这个特点简直可以当做 bug 来看待了。为什么这么说呢?</p>
<p><strong>我们先看看 M-S 架构下,使用内存表存在的问题。</strong></p>
<p><img src="assets/5b910e4c0f1afa219aeecd1f291c95e9.jpg" alt="img" /></p>
<p>图 8 M-S 基本架构</p>
<p>我们来看一下下面这个时序:</p>
<ol>
<li>业务正常访问主库;</li>
<li>备库硬件升级,备库重启,内存表 t1 内容被清空;</li>
<li>备库重启后,客户端发送一条 update 语句,修改表 t1 的数据行,这时备库应用线程就会报错“找不到要更新的行”。</li>
</ol>
<p>这样就会导致主备同步停止。当然,如果这时候发生主备切换的话,客户端会看到,表 t1 的数据“丢失”了。</p>
<p>在图 8 中这种有 proxy 的架构里,大家默认主备切换的逻辑是由数据库系统自己维护的。这样对客户端来说,就是“网络断开,重连之后,发现内存表数据丢失了”。</p>
<p>你可能说这还好啊,毕竟主备发生切换,连接会断开,业务端能够感知到异常。</p>
<p>但是,接下来内存表的这个特性就会让使用现象显得更“诡异”了。由于 MySQL 知道重启之后内存表的数据会丢失。所以担心主库重启之后出现主备不一致MySQL 在实现上做了这样一件事儿:在数据库重启之后,往 binlog 里面写入一行 DELETE FROM t1。</p>
<p><strong>如果你使用是如图 9 所示的双 M 结构的话:</strong></p>
<p><img src="assets/4089c9c1f92ce61d2ed779fd0932ba57.jpg" alt="img" /></p>
<p>图 9 双 M 结构</p>
<p>在备库重启的时候,备库 binlog 里的 delete 语句就会传到主库,然后把主库内存表的内容删除。这样你在使用的时候就会发现,主库的内存表数据突然被清空了。</p>
<p>基于上面的分析,你可以看到,内存表并不适合在生产环境上作为普通数据表使用。</p>
<p>有同学会说,但是内存表执行速度快呀。这个问题,其实你可以这么分析:</p>
<ol>
<li>如果你的表更新量大那么并发度是一个很重要的参考指标InnoDB 支持行锁,并发度比内存表好;</li>
<li>能放到内存表的数据量都不大。如果你考虑的是读的性能,一个读 QPS 很高并且数据量不大的表,即使是使用 InnoDB数据也是都会缓存在 InnoDB Buffer Pool 里的。因此,使用 InnoDB 表的读性能也不会差。</li>
</ol>
<p>所以,**我建议你把普通内存表都用 InnoDB 表来代替。**但是,有一个场景却是例外的。</p>
<p>这个场景就是,我们在第 35 和 36 篇说到的用户临时表。在数据量可控,不会耗费过多内存的情况下,你可以考虑使用内存表。</p>
<p>内存临时表刚好可以无视内存表的两个不足,主要是下面的三个原因:</p>
<ol>
<li>临时表不会被其他线程访问,没有并发性的问题;</li>
<li>临时表重启后也是需要删除的,清空数据这个问题不存在;</li>
<li>备库的临时表也不会影响主库的用户线程。</li>
</ol>
<p>现在,我们回过头再看一下第 35 篇 join 语句优化的例子,当时我建议的是创建一个 InnoDB 临时表,使用的语句序列是:</p>
<pre><code>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&gt;=1 and b&lt;=2000;
select * from t1 join temp_t on (t1.b=temp_t.b);
</code></pre>
<p>了解了内存表的特性,你就知道了, 其实这里使用内存临时表的效果更好,原因有三个:</p>
<ol>
<li>相比于 InnoDB 表,使用内存表不需要写磁盘,往表 temp_t 的写数据的速度更快;</li>
<li>索引 b 使用 hash 索引,查找的速度比 B-Tree 索引快;</li>
<li>临时表数据只有 2000 行,占用的内存有限。</li>
</ol>
<p>因此,你可以对[第 35 篇文章]的语句序列做一个改写,将临时表 t1 改成内存临时表,并且在字段 b 上创建一个 hash 索引。</p>
<pre><code>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&gt;=1 and b&lt;=2000;
select * from t1 join temp_t on (t1.b=temp_t.b);
</code></pre>
<p><img src="assets/a468ba6d14ea225623074b6255b99f92.png" alt="img" /></p>
<p>图 10 使用内存临时表的执行效果</p>
<p>可以看到,不论是导入数据的时间,还是执行 join 的时间,使用内存临时表的速度都比使用 InnoDB 临时表要更快一些。</p>
<h1>小结</h1>
<p>今天这篇文章,我从“要不要使用内存表”这个问题展开,和你介绍了 Memory 引擎的几个特性。</p>
<p>可以看到,由于重启会丢数据,如果一个备库重启,会导致主备同步线程停止;如果主库跟这个备库是双 M 架构,还可能导致主库的内存表数据被删掉。</p>
<p>因此,在生产上,我不建议你使用普通内存表。</p>
<p>如果你是 DBA可以在建表的审核系统中增加这类规则要求业务改用 InnoDB 表。我们在文中也分析了,其实 InnoDB 表性能还不错,而且数据安全也有保障。而内存表由于不支持行锁,更新语句会阻塞查询,性能也未必就如想象中那么好。</p>
<p>基于内存表的特性,我们还分析了它的一个适用场景,就是内存临时表。内存表支持 hash 索引,这个特性利用起来,对复杂查询的加速效果还是很不错的。</p>
<p>最后,我给你留一个问题吧。</p>
<p>假设你刚刚接手的一个数据库上,真的发现了一个内存表。备库重启之后肯定是会导致备库的内存表数据被清空,进而导致主备同步停止。这时,最好的做法是将它修改成 InnoDB 引擎表。</p>
<p>假设当时的业务场景暂时不允许你修改引擎,你可以加上什么自动化逻辑,来避免主备同步停止呢?</p>
<p>你可以把你的思考和分析写在评论区,我会在下一篇文章的末尾跟你讨论这个问题。感谢你的收听,也欢迎你把这篇文章分享给更多的朋友一起阅读。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/MySQL实战45讲/37 什么时候会使用内部临时表?.md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/MySQL实战45讲/39 自增主键为什么不是连续的?.md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"709972e16e023d60","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>