曹耘豪的博客

MySQL之InnoDB

  1. InnoDB数据结构
  2. 索引类型
    1. 聚簇索引
    2. 非聚簇索引(普通索引、辅助索引)
      1. Change Buffer
    3. 唯一索引
    4. 联合索引
  3. 索引检索
    1. 索引失效
    2. 联合索引的最左匹配原则
  4. 日志与两阶段提交
    1. Redo Log(重做日志)
      1. Log结构
      2. 文件结构
      3. LSN(log sequence number)
      4. 刷盘时机
    2. binlog
      1. 写入机制
    3. 二阶段提交
    4. Undo log(回滚日志)
      1. 文件结构
    5. 多版本并发控制
      1. ReadView
      2. 快照读和当前读
    6. 日志总结
  5. Update和Delete操作的内部机制
  6. 磁盘存储
  7. B+Tree结构
  8. 参考

InnoDB数据结构

索引类型

聚簇索引非聚簇索引都是B+Tree,区别是聚簇索引的叶子节点存放整行数据

聚簇索引

非聚簇索引(普通索引、辅助索引)

Change Buffer

用于存储SQL变更操作。每个变更都会对应一个数据页。仅适用于普通索引

插入/更新/删除,使用Change Buffer条件:

Change Buffer合并时机:

唯一索引

联合索引

对于联合索引(a, b, c)

索引检索

索引失效

以下情况不会使用索引:

联合索引的最左匹配原则

必须按照索引定义从左到右匹配

比如,对于联合索引(a, b, c)

日志与两阶段提交

Redo Log(重做日志)

redo log(重做日志)是InnoDB存储引擎独有的,它让MySQL拥有了崩溃恢复能力。

一个事务中,对数据库的每次修改会先写入redo log buffer,一次数据修改往往会有B+Tree的多个修改点,有多条redo log,称为Mini-Transaction(简写mtr),是写入的最小单元。每一组redo log结尾会有一个标志符,直到读到这个标志符这一组才完整,否则丢弃,保证mtr的原子性。

Log结构

{log block header,12字节} + {log block body} + {log block tralider,4字节}

文件结构

redo log不是只有一个文件(数量由innoDB_log_files_in_group指定),而是以redo日志文件组(redo log group)的形式,每个日志文件大小48M(由Innodb_log_file_Size指定),log文件每次以block为单位操作,每个block有512个字节

日志文件组每个文件大小一样,格式一样,由两个部分组成:

记录时采用环形数组的方式,循环写这些文件,所以我们需要知道当前写入的位置和哪些日志已经写到磁盘了,由log_sys记录

LSN(log sequence number)

LSN是一个单调递增的值,从8704开始,脏页(页被修改但未同步至磁盘)和redo log都有各自的LSN。

Buffer Pool里的脏页,是一个链表结构(Flush List)。第一次被修改的页,会以头插法的形式插入到表头,每个脏页都记录着第一次被修改的LSN(123)和最后一次被修改的LSN(123)。

刷盘时机

binlog

binlog是MySQL的Server层实现的,所有引擎都可以使用,有以下作用

binlog记录了这个语句的原始逻辑,格式有3个选项,通过binlog_format指定

写入机制

二阶段提交

redo log的写入分为两个阶段:preparecommit

写入流程:

如果redo log commit阶段失败,由于有binlog,也认为事务完成

Undo log(回滚日志)

Undo log也是InnoDB的一种日志。事务内所有的修改会先记录到undo log并落盘,如果事务执行过程异常,利用undo log回滚

文件结构

多版本并发控制

ReadView

read view 是实现 MVCC 的关键,描述的是在该事务开始时刻数据库系统中所有事务的总体状态。

在新事务开始时,会存在一个已提交事务 T,在其之前的事务都已提交。该事务只能看到 T 及其之前事务的影响(事务 T 提交时刻数据库的 snapshot)

例如,此时的事务提交情况如下,当前事务是7,事务开始时,复制当前的事务总体状况m_ids=[5, 8, 9],此时最大事务已经max_trx_id=10,所以,m_up_limit_id=5m_low_limit_id=10

1
2
3
trx_id: 1 2 3 4 5 6 7 8 9 *10 11 12
state : 1 1 1 1 0 0 _ 1 0 0 1 0
// 0代表正在进行的事务,1代表事务完成

对于事务7,当前正在进行的事务是

对于事务7,已经提交的事务是

综上,对于事务7,可见的事务为[1, 2, 3, 4, 8],就是在7开始时已经提交的事务

数据的隐藏列DATA_TRX_ID记录当前该行的事务ID,当该行的事务对其(事务7)不可见时,使用数据隐藏列DB_ROLL_ID回溯undo log直到可见

快照读和当前读

快照读(SnapShot Read)是一种一致性不加锁的读 ,是InnoDB并发如此之高的核心原因之一

这里的一致性是指,事务读取到的数据,要么是事务开始前就已经存在的数据 ,要么是事务自身插入或者修改过的数据

不加锁的简单SELECT都属于快照读,例如:

1
SELECT * FROM t WHERE id=1`

与快照读相对应的则是当前读, 当前读就是读取最新数据,而不是历史版本的数据。加锁的SELECT就属于当前读,例如:

1
2
3
SELECT * FROM t WHERE id=1 LOCK IN SHARE MODE;

SELECT * FROM t WHERE id=1 FOR UPDATE;

日志总结

MySQL InnoDB引擎使用redo log保证事务的持久性,使用undo log来保证事务的原子性

MySQL数据库的数据备份主备主主主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性

Update和Delete操作的内部机制

update分为两种情况

delete操作实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的

磁盘存储

MySQL一次IO读取的数据称之为一,每页默认是16KB。指针(64位,8B)+值(bigint,8B)=16B,16KB/16B=1K个键值,两层就是1K*1K=100w条记录,对于100w记录的表两次IO即可找到对应的位置

磁盘IO,磁盘读取数据靠的是机械运动,每一次读取数据需要寻道寻点拷贝到内存三步操作,平均需要10ms左右

B+Tree结构

BTree,又叫多路平衡查找树,一颗m叉的BTree特性如下:

参考

   / 
  ,