InnoDB 引擎学习笔记

202 阅读7分钟

InnoDB 存储引擎

InnoDB 存储引擎支持事务,设计目标主要面向在线事务的应用。其特点是行锁涉及、支持外键,并支持非锁定读,从 MySQL 5.5.8 版本开始,InnoDB 存储引擎是默认存储引擎。

InnoDB 适用多版本并发控制(MVCC)来获得高性能,同时实现了 4 中隔离级别,默认为 REPEATABLE 级别。同时,使用一种被称为 next-keylocking 的策略来避免幻读现象的产生。

除此之外 InnoDB 引擎还提供了插入缓冲(insert buffer)、二次写(double write)、自适应哈希索引(adaptive hash index)、预读(read ahead)等高性能和高可用功能,对于表中的数据,InnoDB 采用聚集(clustered)的方式,因此每张表的存储都是按照主键顺序进行存放的,如果没有显式的指定主键,那么 InnoDB 存储引擎会为每一行生成一个 6 字节的 ROWID,并以此作为主键。

MVCC

InnoDB 引擎中有一个全局唯一的 id,transaction id,它实在事务开始的时候想 InnoDB 引擎申请的,按照顺序递增,每行数据也都是由多个版本的,在事务更新数据的时候,都会生产一个新的数据版本,并把 transaction id 记录在 row trx_id,InnoDB 为每个事务构造了一个数组,在事务启动的时候会将当前所有活跃的事务 id 保存到数组中,活跃指的是事务以及开始但还未提交,数组里面最小的事务 id 当作低水位,当前系统已经创建过事务 id 的值 +1 作为高水位,这个数据和高水位就组成了当前事务的一致性视图(read-view),如果所访问数据的版本在低水位则可以直接看到,如果在高水位那么就是不可见,如果在中间则判断事务id是否在活跃数组中,如果在不可见,需要将这一行最新的数据应用 undo log 回退至可见的版本。

更新数据都是先读后写的,而这个读只能是当前读,也就是所在你用以下类似的语句时,假设初始值 k=0,下面事务 A 的操作,如果有一个事务 B 先对 k 进行了更新操作,k = k + 1 但还未提交,后续事务 A 在执行的过程中,读到的 k 的值为最新的值 k = 1,所以会将 k 的值更新为 2。

UPDATE table
SET k = k + 1;

插入缓冲(insert buffer)

insert buffer 的作用适用于非唯一的辅助索引,当涉及到非唯一辅助索引的插入时,如果在 buffer pool 找不到辅助索引对应的页时,InnoDB 会先将插入语句放到 insert buffer 中,然后再以一定的频率和情况将 insert buffer 和辅助索引页子节点的 merge 操作,这是通常能将多个插入合并到一个操作中(因为在同一个索引页),这样就大大的提高了对非聚簇索引插入的性能。

insert buffer 本身是一个B+树的数据结构,非叶子节点存放所在表的表 id(space id),marker 用于兼容老版本 insert buffer,offset 表示页所在的偏移量,叶子节点则存放上面三个之外还多了个 metadata 以及二级索引的数据。

合并操作会在以下三种情况下发生:

  • 辅助索引被读到缓冲区时
    • 用户线程选择二级索引查询时,这时候必须要读入二级索引页,相应的 ibuf entry 需要 merge 到 Page 中
  • Insert Buffer Bitamp 检测到该辅助索引页没有可用空间时
  • Master Thread 每秒或者每隔 10 秒会进行 merge 操作

二次写(double write)

如果说 insert buffer 带来的是性能上的提升,那么 double write 带给 InnoDB 存储引擎的是数据页的可靠性,在没有应用该项技术之前,InnoDB 存储引擎存在部分写失效问题(partial page write),比如说一个 16KB 的页,只写了前 4 KB,之后就发生了宕机。

这里可能会有个疑惑,为什么不能用 redolog 重做日志进行恢复,这是因为重做日志中记录的是对页的物理操作,如偏移量 800,写 'aaa' 的记录,但如果是这个页本身已经发生了损坏,在对其进行重做是没有意义的。所以需要这么一个页的副本,当发生写入失效的时候,可以应用这个副本来进行恢复。

doublewrite 由两个部分组成,一个是内存中的 doublewrite buffer,另一个则是在共享空间表中的连续 128 个页,两个大小均为 2 MB。InnoDB 在对缓冲池中的脏页进行刷新时,并不直接写入磁盘上,而是先通过函数 memcpy 将脏页复制到 doublewrite buffer 中,之后通过 doublewrite buffer 在分两次,每次 1MB 的顺序写入到共享空间表中,以此来避免写失效的问题。

自适应哈希索引(adaptive hash index)

哈希(hash)是一种非常快速的查找方法,只需要一次就能查找到,时间复杂度为 O(1)O(1) ,而 B+ 树的查找次数则是根据树的高度来决定的,一般是 3 ~ 4 次,InnoDB 引擎会监测对表上各个索引页的查询,它会自动的为一些热点数据页建立哈希索引,但是这只适用于等值查询,类似于范围的查询则不会简历哈希索引。

预读(read ahead)

异步 IO

当前数据库都在用异步 IO 的方式来处理磁盘操作,AIO 的一个优势就是可以将多个 IO 操作合并为 1 个 IO 操作,也就是 IO Merge。

刷新邻接页

当 InnoDB 引擎从缓冲池中刷新脏页时,会检测该页所在区(extent)的所有页是否存在其他脏页,如果有的话则一起进行刷新,这样做的好处就是可以通过 AIO 将多次 IO 操作合并,一般在传统机械硬盘下建议开启该功能,固态硬盘则建议关闭。

redo log

redo log 又称重做日志,它是用来保障事务的持久性的,即事务 ACID 中的 D,其由两个部分组成,一个是内存中的 redo log buffer,另一个是重做日志文件 redo log file,其是持久的。

InnoDB 存储引擎通过 Force Log at Commit 机制实现事务的持久性,即当事务提交时(COMMIT),必须先将该事务的所有日志写入重做日志文件后,待事务的 COMMIT 操作完成才算完成。

在 InnoDB 存储引擎中,重做日志是以 512 字节存储的,这意味着它们是以块(block)存储的,称之为重做日志块,此外由于重做日志快大小与磁盘扇区大小一样为 512 字节,因此重做日志的写入可以保证原子性,不需要 doublewrite

每个重做日志都会保存一个 LSN,用于记录日志的序列号,它可以用作记录重做日志的写入量,checkpoints 的位置以及页的版本号,当数据库重启时,无论它是否正常关闭,InnoDB 都会尝试恢复操作,由于 checkpoints 表示以及刷新到磁盘上的 LSN 版本号,所以恢复过程中仅需要从 checkpoints 开始恢复就行

undo log

重做日志记录了事务的行为,而 undo log 则记录了对事物的回滚操作,undo log 是逻辑日志,对于每个 INSERT 会生成对应的 DELETE,对于每个 DELETE 会生成相应的 INSERT,对于每个 UPDATE 会生成与之相反的 UPDATE,同时最重要的一点是 undo log 也会产生 redo log