gitbook/Java性能调优实战/docs/121710.md
2022-09-03 22:05:03 +08:00

108 lines
9.3 KiB
Markdown
Raw Permalink 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.

# 40 | 答疑课堂MySQL中InnoDB的知识点串讲
你好,我是刘超。
模块六有关数据库调优的内容到本周也正式结束了今天我们一起串下MySQL中InnoDB的知识点。InnoDB存储引擎作为我们最常用到的存储引擎之一充分熟悉它的的实现和运行原理有助于我们更好地创建和维护数据库表。
## InnoDB体系架构
InnoDB主要包括了内存池、后台线程以及存储文件。内存池又是由多个内存块组成的主要包括缓存磁盘数据、redo log缓冲等后台线程则包括了Master Thread、IO Thread以及Purge Thread等由InnoDB存储引擎实现的表的存储结构文件一般包括表结构文件.frm、共享表空间文件ibdata1、独占表空间文件ibd以及日志文件redo文件等等。
![](https://static001.geekbang.org/resource/image/f2/92/f26b2fad64a9a527b5ac0e8c7f4be992.jpg)
### 1\. 内存池
我们知道,如果客户端从数据库中读取数据是直接从磁盘读取的话,无疑会带来一定的性能瓶颈,缓冲池的作用就是提高整个数据库的读写性能。
客户端读取数据时如果数据存在于缓冲池中客户端就会直接读取缓冲池中的数据否则再去磁盘中读取对于数据库中的修改数据首先是修改在缓冲池中的数据然后再通过Master Thread线程刷新到磁盘上。
理论上来说,缓冲池的内存越大越好。我们在[第38讲](https://time.geekbang.org/column/article/120160)中详细讲过了缓冲池的大小配置方式以及调优。
缓冲池中不仅缓存索引页和数据页还包括了undo页插入缓存、自适应哈希索引以及InnoDB的锁信息等等。
InnoDB允许多个缓冲池实例从而减少数据库内部资源的竞争增强数据库的并发处理能力[第38讲](https://time.geekbang.org/column/article/120160)还讲到了缓冲池实例的配置以及调优。
InnoDB存储引擎会先将重做日志信息放入到缓冲区中然后再刷新到重做日志文件中。
### 2\. 后台线程
Master Thread 主要负责将缓冲池中的数据异步刷新到磁盘中除此之外还包括插入缓存、undo页的回收等IO Thread是负责读写IO的线程而Purge Thread主要用于回收事务已经提交了的undo logPager Cleaner Thread是新引入的一个用于协助Master Thread刷新脏页到磁盘的线程它可以减轻Master Thread的工作压力减少阻塞。
### 3\. 存储文件
在MySQL中建立一张表都会生成一个.frm文件该文件是用来保存每个表的元数据信息的主要包含表结构定义。
在InnoDB中存储数据都是按表空间进行存放的默认为共享表空间存储的文件即为共享表空间文件ibdata1。若设置了参数innodb\_file\_per\_table为1则会将存储的数据、索引等信息单独存储在一个独占表空间因此也会产生一个独占表空间文件ibd。如果你对共享表空间和独占表空间的理解还不够透彻接下来我会详解。
而日志文件则主要是重做日志文件,主要记录事务产生的重做日志,保证事务的一致性。
## InnoDB逻辑存储结构
InnoDB逻辑存储结构分为表空间Tablespace、段(Segment)、区(Extent)、页Page)以及行(row)。
![](https://static001.geekbang.org/resource/image/88/76/88b4ae3373eb5428c238b70423a13e76.jpg)
### 1\. 表空间Tablespace
InnoDB提供了两种表空间存储数据的方式一种是共享表空间一种是独占表空间。 InnoDB 默认会将其所有的表数据存储在一个共享表空间中即ibdata1。
我们可以通过设置innodb\_file\_per\_table参数为11代表独占方式开启独占表空间模式。开启之后每个表都有自己独立的表空间物理文件所有的数据以及索引都会存储在该文件中这样方便备份以及恢复数据。
### 2\. 段(Segment)
表空间是由各个段组成的段一般分为数据段、索引段和回滚段等。我们知道InnoDB默认是基于B +树实现的数据存储。
这里的索引段则是指的B +树的非叶子节点而数据段则是B +树的叶子节点。而回滚段则指的是回滚数据之前我们在讲事务隔离的时候就介绍到了MVCC利用了回滚段实现了多版本查询数据。
### 3\. 区(Extent) / 页Page
区是表空间的单元结构每个区的大小为1MB。而页是组成区的最小单元页也是InnoDB存储引擎磁盘管理的最小单元每个页的大小默认为16KB。为了保证页的连续性InnoDB存储引擎每次从磁盘申请4-5个区。
### 4\. 行Row
InnoDB存储引擎是面向行的row-oriented)也就是说数据是按行进行存放的每个页存放的行记录也是有硬性定义的最多允许存放16KB/2-200行即7992行记录。
## InnoDB事务之redo log工作原理
InnoDB是一个事务性的存储引擎而InnoDB的事务实现是基于事务日志redo log和undo log实现的。redo log是重做日志提供再写入操作实现事务的持久性undo log是回滚日志提供回滚操作保证事务的一致性。
redo log又包括了内存中的日志缓冲redo log buffer以及保存在磁盘的重做日志文件redo log file前者存储在内存中容易丢失后者持久化在磁盘中不会丢失。
InnoDB的更新操作采用的是Write Ahead Log策略即先写日志再写入磁盘。当一条记录更新时InnoDB会先把记录写入到redo log buffer中并更新内存数据。我们可以通过参数innodb\_flush\_log\_at\_trx\_commit自定义commit时如何将redo log buffer中的日志刷新到redo log file中。
在这里我们需要注意的是InnoDB的redo log的大小是固定的分别有多个日志文件采用循环方式组成一个循环闭环当写到结尾时会回到开头循环写日志。我们可以通过参数innodb\_log\_files\_in\_group和innodb\_log\_file\_size配置日志文件数量和每个日志文件的大小。
Buffer Pool中更新的数据未刷新到磁盘中该内存页我们称之为脏页。最终脏页的数据会刷新到磁盘中将磁盘中的数据覆盖这个过程与redo log不一定有关系。
只有当redo log日志满了的情况下才会主动触发脏页刷新到磁盘而脏页不仅只有redo log日志满了的情况才会刷新到磁盘以下几种情况同样会触发脏页的刷新
* 系统内存不足时,需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘;
* MySQL认为空闲的时间这种情况没有性能问题
* MySQL正常关闭之前会把所有的脏页刷入到磁盘这种情况也没有性能问题。
在生产环境中如果我们开启了慢SQL监控你会发现偶尔会出现一些用时稍长的SQL。这是因为脏页在刷新到磁盘时可能会给数据库带来性能开销导致数据库操作抖动。
![](https://static001.geekbang.org/resource/image/2a/fd/2a32f1dc2dfbb1f9bc169ee55174d2fd.jpg)
## LRU淘汰策略
以上我们了解了InnoDB的更新和插入操作的具体实现原理接下来我们再来了解下读的实现和优化方式。
InnoDB存储引擎是基于集合索引实现的数据存储也就是除了索引列以及主键是存储在B +树之外其它列数据也存储在B + 树的叶子节点中。而这里的索引页和数据页都会缓存在缓冲池中在查询数据时只要在缓冲池中存在该数据InnoDB就不用每次都去磁盘中读取页从而提高数据库的查询性能。
虽然缓冲池是一个很大的内存区域但由于存放了各种类型的数据加上存储数据量之大缓冲池无法将所有的数据都存储在其中。因此缓冲池需要通过LRU算法将最近且经常查询的数据缓存在其中而不常查询的数据就淘汰出去。
InnoDB对LRU做了一些优化我们熟悉的LRU算法通常是将最近查询的数据放到LRU列表的首部而InnoDB则是将数据放在一个midpoint位置通常这个midpoint为列表长度的5/8。
这种策略主要是为了避免一些不常查询的操作突然将热点数据淘汰出去,而热点数据被再次查询时,需要再次从磁盘中获取,从而影响数据库的查询性能。
如果我们的热点数据比较多我们可以通过调整midpoint值来增加热点数据的存储量从而降低热点数据的淘汰率。
## 总结
以上InnoDB的实现和运行原理到这里就介绍完了。回顾模块六前三讲我主要介绍了数据库操作的性能优化包括SQL语句、事务以及索引的优化接下来我又讲到了数据库表优化包括表设计、分表分库的实现等等最后我还介绍了一些数据库参数的调优。
总的来讲作为开发工程师我们应该掌握数据库几个大的知识点然后再深入到数据库内部实现的细节这样才能避免经常写出一些具有性能问题的SQL培养调优数据库性能的能力。
这一讲的内容就到这里,相对基础,不熟悉的同学抓紧补补课,如有疑问,欢迎留言讨论。也欢迎你点击“请朋友读”,把今天的内容分享给身边的朋友,邀请他一起学习。