MySQL(二)InnoDB的内存结构和特性

 

目录

In-Memory Structures

缓冲池 Buffer Pool

更新缓冲Change Buffer

Adaptive Hash Index

(redo)Log Buffer

总结

磁盘结构On-Disk Structures

存储结构

后台线程

BinLog

两阶段提交实现



MySQL区别于其他数据库的最为重要的特点就是其插件式的表存储引擎。而在众多存储引擎中,InnoDB是最为常用的存储引擎。从MySQL5.5.8版本开始,InnoDB存储引擎是默认的存储引擎。

InnoDB存储引擎支持事务,其特点是行锁设计、支持外键,并支持非锁定读,即默认读操作不会产生锁。InnoDB结构主要可分为内存结构和磁盘结构

https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html

In-Memory Structures

内存结构可以分成多个部分: Buffer Pool、Change Buffer、Adaptive Hash Index,(redo)log buffer

缓冲池 Buffer Pool

InnoDB存储引擎是基于磁盘存储的,也就是说数据都是存储在磁盘上的,由于 CPU 速度和磁盘速度之间的有非常大的出入, InnoDB 引擎使用缓冲池技术来提高数据库的整体性能。
InnoDB操作数据有一个最小的逻辑单位,叫做页。InnoDB会把磁盘读到的页放到一块内存区域里面。这个内存区域就叫Buffer Pool。Buffer Pool缓存的是页面信息,包括数据页、索引页。下一次再读取相同的页时,就先判断是否已经在缓冲池里面,如果是,就直接读取,不用再访问磁盘

对于数据库中页的修改操作,首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。
为了提高读取操作的效率,缓冲池分为多个页,这些页可以容纳多行数据。为了提高缓存管理的效率,缓冲池是以页的链表方式实现的,使用LRU算法的将很少使用的数据页从缓存中删除。

Buffer Pool默认大小是128M(134217728字节),可以通过SHOW VARIABLES like'%innodb_buffer_pool%'; 查看

更新缓冲Change Buffer

一般情况下,主键是行唯一的标识符。通常应用程序中行记录的插入顺序是按照主键递增的顺序进行插入的。因此,插入聚集索引一般是顺序的,不需要磁盘的随机读取。因为,对于此类情况下的插入,速度还是非常快的。(如果主键类是UUID这样的类,那么插入和辅助索引一样,也是随机的。)

如果索引是非聚集的且不唯一。在进行插入操作时,数据的存放对于非聚集索引叶子节点的插入不是顺序的,这时需要离散地访问非聚集索引页,由于需要随机读取页,从而导致插入操作性能下降。

当需要更新一个数据页时,如果数据页在BufferPool中不存在,那么就需要从磁盘加载到内存,再对内存的数据页进行操作。也就是说,如果没有命中缓冲池,至少要产生一次磁盘IO。对于这种情形,InnoDB也有对应的优化方式,就是Change Buffer

如果这个数据页不存在唯一索引,不需要担心出现数据重复的情况,那么就不需要从磁盘加载索引页进行唯一性检查。这样我们就可以先把想要修改的内容存放在内存的缓冲池中,从而提升更新语句的执行速度。
这一块区域就是 Change Buffer , 5.5 之前叫Insert Buffer 插入缓冲.

最后再把 Change Buffer 里的数据页批量写入到磁盘,这个过程称之为merge,在以下几个情形下会触发merge操作:

(1)redo log写满时,此时需要将checkpoint向前推进,推进的这部分日志对应的脏页需要刷入到磁盘,此时所有的更新全部阻塞,必须待刷一部分脏页后才能更新。
(2)系统内存不足时,需要将一部分数据页淘汰掉,如果淘汰的是脏页,需要先将脏页同步到磁盘。
(3) MySQL认为空闲的时间
(4) mysql正常关闭之前,会把所有脏页刷入磁盘

如果数据库大部分索引都是非唯一索引,并且业务是写多读少,不会在写数据后立刻读取,就可以使用Change Buffer。如果是写多读少的业务,可以适当调大ChangerBuffer占Buffer Pool的比例,默认值是25%。
SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';

Adaptive Hash Index

哈希是一种非常快的查找方法,在一般情况时间复杂度为O(1)。而B+树的查找次数,取决于B+树的高度,在生产环境中,B+树的高度一般为3-4层,只需要查询3-4次。

InnoDB utilizes hash indexes internally for its Adaptive Hash Index feature

直接翻译过来就是:InnoDB内部使用哈希索引来实现自适应哈希索引特性。
这句话的意思是 InnoDB 只支持显式创建 B+树 索引,对于一些热点数据页,InnoDB会自动建立自适应Hash索引,也就是当InnoDB注意到某些索引值被使用的非常频繁时,它会在内存中基于B+树索引上再创建一个hash索引,这样就让B+树索引也具有hash索引的一些优点。
这个过程对于客户端是不可控制的,隐藏的,由InnoDB自己内部完成

(redo)Log Buffer

缓冲池的存在提高了InnoDB查询和更新数据的速度,但是不可能避免的也会引发一定的安全性问题,当缓冲池的数据比磁盘数据要新时,如果不及时把缓冲池中新版本的页数据同步到磁盘,一旦出现断电等事故,缓冲池中更新的数据就会丢失。但是如果每次一个页发送变化,就进行刷新,那么性能消耗是非常大的。

为了避免这个问题, InnoDB把所有对页面的修改操作专门写入一个日志文件,并且在数据库启动时从这个文件进行恢复操作(实现crash-safe)——用它来实现事务的持久性。
这种日志和磁盘配合的整个过程,其实就是 MySQL 里的 WAL 技术(Write-Ahead Logging),其关键点就是先写日志,再写磁盘,即当事务提交时,先写redo log日志,然后再将脏页(当内存数据页和磁盘数据页上的内容不一致时,我们称这个内存页为脏页)写入磁盘。如果发生宕机导致数据丢失,就可以通过redo log日志来进行数据恢复。

redo log对应于/var/lib/mysql/目录下的ib_logfile0和ib_logfile1,每个大小为48M

同样是写磁盘,为什么InnoDB选择写入到redo log里,而不是直接写入到磁盘文件?

这是因为顺序IO比随机IO更快 理解顺序IO和随机IO
刷盘是随机I/O,而日志记录是顺序I/O,顺序I/O的效率更高。因此先把修改写入日志,这样可以延迟刷盘时机又不用担心宕机导致数据丢失,进而提升系统吞吐量。

为了减少写日志的磁盘IO , redo log也不是每一次数据更新都直接写入磁盘,在Buffer Pool里面有一块内存区域(Log Buffer)专门用来保存即将要写入日志文件的数据,默认大小为16M

Log Buffer写入到redo log的时机,由一个参数控制,默认是1(SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';)
注意:在我们写入数据到磁盘的时候,操作系统本身是有缓存的。这里的flush操作就是把操作系统缓冲区的数据写入到磁盘。

注意:这里所说的log file并不是磁盘上的物理日志文件,而是操作系统缓存中的log file

这里的redo log的大小是固定的,当不断加入新的日志时,前面的内容会被覆盖

这里write pos表示日志当前记录的位置,当ib_logfile_4写满后,会从ib_logfile_1从头开始记录;check point当前日志将要被擦除的位置,如果日志记录的修改已经被写进数据库文件,checkpoint会将日志上的相关记录擦除掉,并向前移动,所以write pos->checkpoint之间的部分是redo log空着的部分,用于记录新的记录。当write pos跟check point重叠,说明redo log已经写满,这时候需要同步redo log到磁盘中

总结

缓冲池 Buffer Pool:缓存访问的页数据
更新缓冲Change Buffer:对于不更新唯一索引的DML,缓存到change buffer然后批量写入到磁盘
Adaptive Hash Index:由InnoDB自己生成,对热点数据页加上哈希索引提高查询效率
(redo)Log Buffer:缓存每次更新操作的日志,定时写入到redo log中,可用于数据恢复

磁盘结构On-Disk Structures

磁盘结构里面主要是各种各样的表空间,叫做Table space。表空间可以看做是InnoDB 存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。InnoDB的表空间分为5大类

系统表空间 system tablespace

在默认情况下 InnoDB 存储引擎有一个共享表空间(对应文件/var/lib/mysql/ibdata1),也叫系统表空间。
InnoDB系统表空间包含InnoDB数据字典和双写缓冲区,ChangeBuffer和Undo Logs,如果表没有指定自己的表空间(file-per-table),那么系统表空间也会包含用户创建的表和索引数据。
undo logs后面介绍,可以有对应的表空间---undo log tablespace
数据字典: 
    InnoDB有自己的表缓存,可以称为表定义缓存或者数据字典。当InnoDB打开一张表,就增加一个对应的对象到数据字典。由内部系统表组成,存储表和索引的元数据(定义信息)。数据字典是对数据库中的数据、库对象、表对象等的元信息的集合。MySQL INFORMATION_SCHEMA库中保存的信息也可以称为MySQL的数据字典。

独占表空间 file-per-table tablespaces

我们可以让每张表独占一个表空间。这个开关通过innodb_file_per_table设置,默认开启。
SHOW VARIABLES LIKE 'innodb_file_per_table';
开启后,则每张表会开辟一个表空间,这个文件就是数据目录下的 ibd文件(例如/var/lib/mysql/db0/user.ibd),存放表的索引和数据

通用表空间 general tablespaces

通用表空间也是一种共享的表空间,跟ibdata1类似。
可以创建一个通用的表空间,用来存储不同数据库的表,数据路径和文件可以自定义。
语法:
create tablespace  tschenpp add datafile '/var/lib/mysql/tschenpp.ibd' file_block_size=16K engine=innodb;
在创建表的时候可以指定表空间,用ALTER修改表空间可以转移表空间。
create table t1 (id integer) tablespace tschenpp ;
删除表空间需要先删除里面的所有表:
drop t1 ; drop tablespace tschenpp ;

临时表空间 temporary tablespaces

存储临时表的数据,包括用户创建的临时表,和磁盘的内部临时表。对应数据目录下的ibtmp1文件。当数据服务器正常关闭时,该表空间被删除,下次重新产生

undo log tablespace

undo log(也称为撤销日志或回滚日志)记录了事务发生之前的数据状态(不考虑DQL语句) 。如果操作数据时出现异常,可以使用undo log来实现回滚操作(保持原子性)。还可以用于MVCC(实现非锁定读),读取一行记录时,若该行已被其他事务锁定,则通过undo log 读取之前的版本。

在执行回滚的时候,仅仅是将数据从逻辑上恢复至事务之前的状态,而不是从物理页面上实现操作的,属于逻辑格式的日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录,会根据每行记录进行记录
undo Log 的数据默认在系统表空间ibdata1 文件中,也可以独自创建一个undo表空间。

redo Log和undo Log与事务密切相关,统称为事务日志。undo log记录某 数据 被修改 的值,可以用来在事务失败时进行 rollback;redo log记录某 数据块 被修改 的值,可以用来恢复未写入 data file 的已成功提交事务的数据。

比如某一时刻数据库宕机了,此时有两个事务,一个事务已经提交,另一个事务正在处理中。数据库重启的时候就要根据日志分别进行数据写入及回滚,把已提交事务的更改写到数据文件,未提交事务的更改恢复到事务开始前的状态。即,当数据 crash-recovery 时,通过 redo log 将所有已经在存储引擎内部提交的事务数据恢复,所有已经 prepared 但是没有 commit 的 事务(什么叫做preoared,commit的事务,这里涉及到binlog和redo log的两阶段提交,下面会介绍下)将会通过 undo log 做回滚。

双写缓冲Doublewrite Buffer(InnoDB的一大特性)

InnoDB的页和操作系统的页大小不一致,InnoDB页大小一般为16K,操作系统页大小为4K,InnoDB的页写入到磁盘时,一个页需要分4次写。
如果存储引擎正在写入页的数据到磁盘时发生了宕机,可能出现页只写了一部分的情况,比如只写了4K,就宕机了,这种情况叫做部分写失效(partial page write),可能会导致数据丢失。

The doublewrite buffer is a storage area in the system tablespace where InnoDB writes pages that are flushed from the buffer pool before writing them to their proper positions in the data file. Only after flushing and writing pages to the doublewrite buffer does InnoDB write pages to their proper positions. If there is an operating system, storage subsystem, or mysqld process crash in the middle of a page write, InnoDB can find a good copy of the page from the doublewrite buffer during crash recovery.

前面说到redo log可以用来做数据恢复,不过如果此时数据页是损坏的(只写入了部分页数据到磁盘文件),那么使用redo log是无法恢复数据的,因为redo log记录的是页的物理修改。此时我们就需要一个完整的页的副本。Doublewrite Buffer就是在写入页数据到磁盘上之前,先将页数据完整写入到磁盘和内存的double write区,只有写入和刷新成功后,才会将页数据写入到数据文件。那么如果出现了写入失败,就先用页的副本来还原这个页,然后再应用redo log。这个就是Double Write,通过它保证了数据页的可靠性

double write 由两部分组成,一部分是内存的double write,一个部分是磁盘上的double write。虽然数据有两次写入操作,由于Doublewrite Buffer在表空间中是连续的区域,因此,I/O开销并不大,只需调用一次fsync()。

为什么redo log的日志写入不需要double write的支持?
因为redolog写入的单位就是512字节,也就是磁盘IO的最小单位,所以无所谓数据损坏。

存储结构

MySQL的存储结构分为5级:表空间、段、簇、页、行

表空间 TableSpace

前面提到过表空间可以看做是InnoDB 存储引擎逻辑结构的最高层,所有的数据都存放在表空间中。分为:系统表空间、独占表空间、通用表空间、临时表空间、Undo表空间。

段 Segment

表空间是由各个段组成的,常见的段有数据段、索引段、回滚段等,段是一个逻辑的概念。一个ibd文件(独立表空间文件)会由很多个段组成。
创建一个索引会创建两个段(即是说,一个表的段数就是索引的个数乘以2。),一个是索引段:non-leaf node segment,一个是数据段: leaf node segment。索引段管理非叶子节点的数据。数据段管理叶子节点的数据。

簇 Extent

一个段(Segment)又由很多的簇(也可以叫区)组成,每个区的大小是1MB(64个连续的页)。
每一个段至少会有一个簇,一个段所管理的空间大小是无限的,可以一直扩展下去,但是扩展的最小单位就是簇(1M)

页Page

页是InnoDB磁盘管理的最小单位,每个页默认大小为16KB,通过参数innodb_page_size可以修改默认页的大小。
一个簇中有64个连续的页。 (1MB/16KB=64)。这些页面在物理上和逻辑上都是连续的。
一个表空间最多拥有2^32个页,默认情况下一个页的大小为16KB,也就是说一个表空间最多存储64TB的数据。

行 Row

InnoDB 存储引擎是面向行的(row-oriented),也就是说数据的存放按行进行存放。每个页存放的行记录也是有硬性定义的,最多允许存放16KB/2-200,即7992行记录(据说是由内核定义的)。

后台线程

内存和磁盘之间,工作着很多后台线程

后台线程的主要作用是负责刷新内存池中的数据和把修改的数据页刷新到磁盘。后台线程分为:master thread,IO thread,purge thread,page cleaner thread。

  • master thread负责刷新缓存数据到磁盘并协调调度其它后台进程。
  • IO thread 分为 insert buffer、 log、 read、 write进程。分别用来处理insert buffer、重做日志、读写请求的IO回调。
  • purge thread用来回收undo 页。
  • page cleaner thread用来刷新脏页

BinLog

除了 InnoDB 架构中的日志文件,MySQL 的 Server 层也有一个日志文件,叫做binlog,它可以被所有的存储引擎使用

binlog以事件的形式记录了所有的DDL和DML语句(因为它记录的是操作而不是数据值,属于逻辑日志),可以用来做主从复制和数据恢复。
跟redo log不一样,它的文件内容是可以追加的,没有固定大小限制。在开启了binlog功能的情况下,我们可以把binlog导出成SQL语句,把所有的操作重新执行一遍,来实现数据的恢复。
binlog的另一个功能就是用来实现主从复制,它的原理就是从服务器读取主服务器的binlog,然后执行一遍。

两阶段提交实现

这个两阶段提交不是分布式事务的两阶段提交,而是在开启binlog之后,redo与binlog的两阶段提交。 两阶段提交:首先redo log写入( prepare状态),然后写binlog,最后redo log 改为commit状态.

如下更新SQL的执行流程为:深色的表示在执行器中执行的,浅色的表示在InnoDB执行引擎内部执行

为什么使用两阶段提交协议呢,这是为了当数据库发生了意外情况,宕机、断点、重启等等,可以保证使用binLog恢复数据和当时数据状态一致;

具体情况下的策略如下:
bin log有记录,redo log状态commit:正常完成的事务,不需要恢复
bin log有记录,redo log状态prepare:在bin log写完提交事务之前的crash,恢复操作:提交事务
bin log无记录,redo log状态prepare:在binlog写完之前的crash,恢复操作:使用undo log回滚事务
bin log无记录,redo log无记录:在redolog写之前crash,恢复操作:使用undo log回滚事务

bin log和redo log的区别:

  • redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用

  • redo log是物理日志,记录的是"在某个数据页上做了什么修改";binlog是逻辑日志,记录的是这个语句的原始逻辑,比如"给ID=2这一行的c字段加1 "

  • redo log是循环写的,空间固定会用完;binlog是可以追加写入的。"追加写"是指binlog文件写到一定大小后会切换到下一个,并不会覆盖以前的日志

参考:

https://dev.mysql.com/doc/refman/5.7/en/innodb-storage-engine.html
https://www.cnblogs.com/binyang/p/11260126.html

发布了58 篇原创文章 · 获赞 19 · 访问量 7455

猜你喜欢

转载自blog.csdn.net/qq_35448165/article/details/104160950
今日推荐