文章目录

  • 1 InnoDB存储引擎原理
  • 2 内存管理
  • 2.1 内存淘汰算法
  • 2.1.1 移动的规则
  • 3 事务
  • 3.1 MVCC
  • 3.1.1 快照读如何实现了RR?
  • 3.2 undolog
  • 3.3 redolog
  • 4 锁
  • 4.1 如何加锁?RR/RC * 唯一索引/非唯一索引
  • 4.1.1 RC & 非唯一索引
  • 4.1.2 RR & 非唯一索引
  • 间隙锁
  • 4.1.3 表级锁
  • 4.1.4 加锁过程
  • 死锁问题

1 InnoDB存储引擎原理

以Page为单位存储,每个Page的结构如下:(每个页的大小为16KB)

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_链表

  • 页头
  • 记录页面的控制信息,共占56字节,包括页的左右兄弟页面指针、页面空间使用情况等。
  • 虚记录
  • 最大虚记录:比页内最大主键还大
  • 最小虚记录:比页内最小主键还小
  • 记录堆
  • 行记录存储区,分为有效记录和已删除记录两种
  • 自由空间链表
  • 已删除记录组成的链表
  • 未分配空间
  • 页面未使用的存储空间;
  • 页尾
  • 页面最后部分,占8个字节,主要存储页面的校验信息;

结构示意图:

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_数据库_02

  • 顺序保证:使用逻辑有序(链表)而非物理有序(数组)
  • 插入策略:自由空间链表+未使用空间。优先使用自由空间链表。
  • 查询策略:
  • 首先找到数据在哪一页;
  • 其次通过二分查找找到需要的那行数据。这里使用了跳表的数据结构:

    slot区是一段连续的存储空间,每个slot大小相同,这样就可以使用二分法找到所需要的记录在哪个子链表上。

2 内存管理

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_mysql_03


MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_加锁_04

2.1 内存淘汰算法

正常使用LRU即可,但是单纯的LRU会存在一个问题:假如我做了一次全表扫描,那么所有的数据都会被加载进内存一次,这样就导致热数据失效。

因此,解决热数据失效可以从两方面考虑:

  • 结合LFU(Least Frequent Used 最少频率使用)来淘汰页面
  • 再来一个LRU队列,这样组成双LRU队列:一个保存热数据,一个保存冷数据。热LRU设定一个规则,达到该规则后热的page会转移到冷LRU中;冷LRU页面也同样设定规则,允许冷page转移到热LRU中。

InnoDB的解决方法:双LRU

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_数据_05

2.1.1 移动的规则

  • old到new:
  • MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_数据_06

  • 从new到old
    直接把new区的尾节点放到old区的头节点即可。
  • MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_数据库_07

  • LRU_new中的操作
    通常来说,LRU_new保存的是最新最近访问的数据,一个数据一旦被访问到,就移动到头部。我们知道链表的插入操作很快,但是这样一直插入头部会存在性能问题吗?需要去优化吗?

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_mysql_08


事实上并不是每个page一访问就移动到头部,而是看这个page在访问的时候它所处的位置。

a. 如果当前page在访问的时候已经到了LRU的1/4之后,它会面临被淘汰的危险。这时就把它移动到头部。

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_链表_09

b. 如果当前page在访问的时候还在LRU的前1/4,则不需要移动到头部。

具体当前Page在LRU的哪个部分,是从该Page上次移动到头部,到当前时刻一共发生了多少次页面淘汰而计算的。

3 事务

一般我们会认为 begin/start transaction 是事务开始的时间点,也就是一旦我们执行了 start transaction,就认为事务已经开始了,其实这是错误的。事务开始的真正的时间点(LSN),是 start transaction 之后执行的第一条语句,不管是什么语句,不管成功与否。

但是如果你想要达到将 start transaction 作为事务开始的时间点,那么我们必须使用:

start transaction with consistent snapshot

它的含义是:执行 start transaction 同时建立本事务一致性读的 snapshot . 而不是等到执行第一条语句时,才开始事务,并且建立一致性读的 snapshot .

效果等价于: start transaction 之后,马上执行一条 select 语句(此时会建立一致性读的snapshot)。

3.1 MVCC

MVCC又叫多版本并发控制。用于解决读写冲突。InnoDB在每个表创建的时候都会追加两个隐藏列:事务ID列(DB_TRX_ID)和回滚指针列(DB_ROLL_PTR)。前者的含义是,ID为x的事务将表中的这条数据变为当前状态。后者可以实现从当前数据从一个事务转移到另一个事务。

两个重要的概念:

  • 当前读:select for update / insert / update / delete。读取最新提交的数据,并且对读取的记录加锁,阻塞其他事务同时修改相同记录,避免出现安全问题。
  • 快照读:select。读取当前时刻可见的数据。不管提交了多少次,始终读取到的都是当前可见的最新历史版本。实现了RR可重复读。

Read Committed隔离级别:每次select都生成一个快照读
Read Repeatable隔离级别:开启事务后第一个select语句才是快照读的地方,而不是一开启事务就快照读

3.1.1 快照读如何实现了RR?

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_mysql_10

  • 每个事务创建的时候,会创建一个read view,记录当前所有活跃的事务(已创建但未提交的事务)。
  • 读取(select)一条数据时,查看该数据的事务ID。
  • 如果小于活跃事务的最小ID,说明产生该数据的事务已经提交,该数据可读
  • 如果大于活跃事务的最大ID,说明产生该数据的事务还没创建,自然在当前事务下是不可读的
  • 如果在最小ID-最大ID之间
  • 如果ID在活跃事务列表里,说明产生该数据的事务还没提交,不可读。
  • 如果ID不在活跃事务列表里,说明已经提交,可读。

如下图,事务A在创建的时刻,它只能读到事务B提交的数据,而无法读到事务C和D提交的数据。

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_数据库_11

3.2 undolog

  • 回滚日志
  • 保证事务原子性
  • 实现数据多版本
  • delete undo log:用于回滚,提交即清理;
  • update undo log:用于回滚,同时实现快照读,不能随便删除

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_mysql_12


如图,第一条数据是保存在存储引擎里的,后面两条保存在undolog里。

思考:undolog如何清理?
答:依照系统活跃的最小活跃事务ID Read view。ID最小的事务可以看到的数据,全局都可以看到,则这些数据以前的版本都可以删除掉。

3.3 redolog

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_数据库_13


刷盘时机:通过一个参数指定。推荐2.

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_链表_14


redolog的意义?

  • 体积小,记录页的修改,比写入页代价低
  • 末尾追加,随机写变顺序写,发生改变的页不固定

4 锁

  • 锁粒度
  • 行级锁
  • 间隙锁
  • 表级锁
  • 类型
  • 共享锁(S)
  • 读锁,可以同时被多个事务获取,阻止其他事务对记录的修改;
  • 排他锁(X)
  • 写锁,只能被一个事务获取,允许获得锁的事务修改数据;所有当前读加排他锁,包括SELECT FOR UPDATE、UPDATE、DELETE

4.1 如何加锁?RR/RC * 唯一索引/非唯一索引

探讨为什么RR隔离级别能够解决幻读。

幻读举例:假如数据库中存在三条记录:{a: 1, b: 2, c: 3}。事务A通过select快照读可以读到这三条数据。这时事务B向数据库中insert了一条数据d:4并提交。此时事务A同样想插入数据d:5,但是没有成功,事务A再select还是看到的abc三条数据。

4.1.1 RC & 非唯一索引

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_数据_15

4.1.2 RR & 非唯一索引

RR级别的幻读是怎么产生的?
还是上面那个例子:delete from user where phone = 134。
事务A执行这句话,显示影响行数2。此时事务A并不提交。
事务B此时执行插入insert into user values(134, 106)
事务A再执行delete语句,发现又有一条影响行数
如果事务B反复执行插入,则事务A每次执行delete都会看到新的影响行

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_mysql_16

问题关键:记录之间的间隙。
解决方法:间隙锁

间隙锁
  • 解决可重复读模式下的幻读问题;
  • GAP锁不是加在记录上;
  • GAP锁锁住的位置,是两条记录之间的GAP;
  • 保证两次当前读返回一致的记录;

间隙锁保证两次当前读之间,其他事务不会插入新的满足条件的记录!

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_数据库_17

4.1.3 表级锁

什么情况下会触发表级锁?全表扫描!

如果phone不是索引,则下面这条语句会全表扫描。扫描时RC隔离级别会把每条记录加锁再释放;RR隔离级别还会给每个Gap加锁再释放。

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_数据库_18


理论上只有事务提交后才会释放锁,但server层做了一个过滤,把没有命中记录的锁都提前释放了。

4.1.4 加锁过程

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_数据_19

死锁问题

MySQL InnoDB存储引擎深入分析与管理 mysql存储引擎实现原理_加锁_20