前言
今天继续来完成补全计划!
InnoDB存储引擎
逻辑存储结构
- 
表空间 表空间是 InnoDB存储引擎逻辑的最高层,当用户启用了参数innodb_file——per_table,则每张表就会有一个表空间,一个mysql实例可以对应多个表空间,用于存储记录、索引。
- 
段 分为数据段、索引段、回滚段, InnoDB是索引组织表,数据段就是B+树的叶子结点。段可以用来管理多个区。
- 
区 每个区的大小为 1M,默认情况下,InnoDB存储引擎页大小为16k,即一个区里面有64个连续的页。
- 
页 页是磁盘管理的最小单元,每个页大小默认为 16kb
- 
行 InnoDB存储数据是按照行进行存放的。在行中,默认有两个隐藏字段: - Trx_id:每次对某条记录进行改动时,都会把对应的事务- id赋值给- trx_id隐藏列
- Roll_pointer:每次对某条记录进行改动时,都会把旧的版本写入- undo日志,然后这个隐藏列就相当于一个指针,可以通过它来找到该记录修改前的信息,也就是可以用这个指针来进行- undo日志的操作。
 
内存结构
主要分为四大块,Buffer Pool、Change buffer、Adaptive Hash Index、Log Buffer
Buffer Pool
缓存池的思想我们已经很熟悉了,如果每次操作都是进行磁盘IO,那么效率会很慢,,我们可以把经常使用的数据加载到缓存池里面,这样就可以避免每次访问的时候都进行磁盘IO。
在执行增删改查操作时,先操作缓存池中的数据,然后再以一定的频率刷新到磁盘中,从而减少磁盘IO,加快处理速度。
Change Buffer
这个其实也是缓存而已,只不过是专门对于二级索引建立的,在修改二级索引有关的数据时,会把修改的遍历提交到这个缓存中,等待某个时机后会与buffer pool合并刷新到磁盘中。
自适应hash
自适应hash索引,用于优化对Buffer Pool数据的查询。
hash索引在进行等值匹配时,一般性能是要高于B+树,但是hash索引又不适合做范围查询、模糊匹配等。
自适应hash可以理解为是InnoDB存储引擎对热点数据、热点页加快访问而自动创建管理的索引。
Log Buffer
日志缓冲区,用来保存需要写入到磁盘中的日志数据。像redo log等。在进行进行修改或删除语句时,mysql会开启自动事务提交。在提交时,为了保证事务的一致性和完整性,我们通常会使用redo log,即将事务写道log buffer中,等待一定时机把这个事务刷新到磁盘里面,这样,就算mysql崩溃了,也会去重新加载磁盘上的redo log,然后更新事务。
🙅自动事务提交不支持事务回滚(RollBack)
事务什么时候提交到磁盘有三种选择:
- 每次事务提交的时候写入缓存并立即刷新到磁盘中,是默认选项
- 每秒将日志刷新到磁盘中
- 日志提交到缓存中后,每秒刷新日志到磁盘中。
磁盘结构

后台线程
主要包括
- Master Thread
- IO Thread
- Purge Thread
- Page Cleaner Thread
Master Thread
核心后台线程,负责调度其它线程,还负责将缓冲池中的数据异步刷新到磁盘中。
IO Thread

可以通过下面这条指令来查看InnoDB的状态信息
|  |  | 
Purge(净化) Thread
用于回收事务已经提交的undo log,在事务提交后,undo log可能就不用了
Page Cleaner Thread
协助Master Thread刷新脏页到磁盘的线程,减轻Master Thread的压力。
事务
事务四大特性
原子性
要么全部成功提交,要么全部失败,不存在中间状态。
一致性
事务完成时,所有数据必须保证一致性。
隔离性
多个事务之间互不影响,而且事务在不受外界影响的隔离环境中运行。
持久性
事务一旦提交或者回滚,它对数据库中的数据改变就是永久的。
对于这四大特性,由实现原理分为两部分,其中原子性、一致性、持久性是通过redo log和undo log保障的。
隔离性是通过数据库的锁和MVCC保证的。

Log日志
最重要的就是redo log和undo log,下面来详细说一下啊这些日志。
Redo log和WAL预写日志
Redo log主要分为两类,一种是存在于磁盘上,另一种就是存在于内存中。
内存中主要是重做缓存的部分,也就是log buffer缓存,当事务提交时,就会把所有修改信息全部保存在缓存中,然后根据一定的刷新时机,把所有信息全部存储在该日志文件中。
可以重载恢复的一部分是存在于磁盘上,从而做到了持久化恢复,当发生错误时,就可以进行数据恢复。
记住,
redo log是帮助Mysql服务器崩溃重启而建立的,当数据库崩溃时,再次重启就可以从磁盘中的redo log日志中加载未提交恢复的事务。
在业务操作中,我们操作数据一般是随机读写磁盘的,而不是顺序读写磁盘的。而redo log在往磁盘中写入文件,由于是日志文件,都是顺序写的。顺序写的效率要远大于随机写。这种先写日志的方式,叫做WAL。
其实上面这段话有些绕,这种方式也算是一种独特的持久化策略。因为每次修改一个数据、删除数据基本上都是随机的,此时,每次都写一个数据实际上就会造成一次随机读取。而对于redo log日志来说,先写入内存中,然后再刷新到磁盘中,对于磁盘中的日志文件来说,就是**(顺序读写的(连续追加**)
Undo log
事务的一个关键特点就是支持回滚
当事务进行回滚时,之前对数据的修改“都不算话”!
其实现思路是反着来,例如,当执行一条insert语句时,就在undo log中保存一条相反语句即可,属于是逻辑日志。
多版本并发控制MVCC
我觉得MVCC就是为各种事务的隔离级别所设置的。实际上,MVCC把各种读写分为多个版本,从而做到可重复读。
当前读
当前读读的是最新的版本,读取时还要确保其它并发事务不能修改当前记录,下面这些操作都是一种当前读
- 
1select ... lock in share mode; -- 共享锁
- 
1 2 3update insert delete -- 排他锁都是一种当前读。 
快照读
简单的select 就是快照读,快照读的意思就是在某个时候对数据进行一个“拍照”,记录下那个时候的数据。
还记得我们的几种事务隔离级别吗?他们解决什么问题来着?
RC,读提交,解决的是“一个线程可以读到另一个线程未提交的事务”的问题,对应问题是脏读
RR,可重复读,解决的是“一个线程前后读到的数据不一致的情况”,对应问题是不可重复读
序列化,解决的是“一个线程之前可以读到数据,之后发现读不了这个数据”的问题,对应的问题是幻读
对于Rc隔离级别,一个线程只可以读到另一个线程已经提交过的事务,所以,当另一个线程的事务未提交时,每次select的时候都生成一个快照读。
对于RR的隔离级别,实际上,为了解决可重复读,一个很容易想到的思路是,在第一次对某个数据进行查询操作的时候,就生成这个数据的快照,这样,每次都去读这个数据的快照,也就解决了“不可重复读”的问题。
对于”序列化“的隔离级别来说,快照读会变为当前读,总是保证事务提交后再去读,也就是说,序列化读到的数据永远都是最正确的,也就解决了幻读。
MVCC
MVCC的含义就是去维护一份数据的多个版本,从而避免了在并发场景下同一份数据被争抢而导致的频繁lock/unlock操作。
下面我们来分析一下InnoDB表中的一些个隐藏字段
隐藏字段
在创建表时,除了我们手动创建的字段类型外,还有一些辅助字段。

undo log
前面已经介绍了一些undo log的基本思想,实际上,当事务成功提交后,所产生的undo log就会被删除。
下面我们来看一看上面这些隐藏字段和undo log是怎么做到事务回滚的吧!
假设有一张表为

在刚开始,这个表的某行数据还没有被修改过,所以也就没有需要回滚的版本。
假设有多个事务在并发执行,我们假设为事务1 事务2 事务3 事务4,当事务1进行数据提交时,我们需要修改三个地方
- 生成对应的undo log日志,以免日后数据回滚
- 修改DB_TRX_ID字段,把这个值改为1,意思是”事务id为1的事务修改了这行数据
- 修改当前数据的DB_ROLL_PTR数据字段,把当前字段指向undo log
同样的,这几个事务在并发执行的时候,也就会轮流修改这些数据,由于事务还未提交,对应的undo log日志也会一直存在,从而变成一个undo log日志链

总结
下次来讨论一下视图以及MVCC实现的一些具体原理。