MySQL技术内幕 :InnoDB存储引擎(第二版)

MySQL架构

  • MySQL是一个单进程多线程架构的数据库

存储引擎

InnoDB存储引擎

  • MySQL5.5.8之后默认的存储引擎,主要面向OLTP
  • 支持事务
  • 支持行锁(有的情况下也会锁住整个表)
  • 非锁定读(默认读取操作不会产生锁)
  • 通过使用MVCC来获取高并发性,并且实现sql标准的4种隔离级别,默认为可重复读级别,使用一种被称成next-key locking的策略来避免幻读(phantom)现象
  • 支持全文索引(InnoDB1.2.x - mysql5.6)
  • 提供了插入缓存(insert buffer)、二次写(double write)、自适应哈希索引(adaptive hash index)、预读(read ahead)等高性能技术
  • 表数据采用聚集方式,每张表的存储都按主键的顺序进行存放。

MyISAM存储引擎

  • 不支持事务,主要面向一些OLAP
  • 支持全文索引
  • 缓冲池只缓冲索引文件,而不缓冲数据文件
  • 存储引擎表由MYD和MYI组成,MYD用来存放数据文件,MYI用来存放索引文件.

NDB存储引擎

  • 是一个集群存储引擎,其特点是数据全部放在内存中。
  • 主键查找速度极快,并通过添加NDB数据库存储节点可以线性提高数据库性能,是高可用,高性能的集群系统

InnoDB体系架构

后台线程

  • Master Thread:负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性
  • IO Thread:负责IO请求的回调处理
  • Purge Thread:回收已经使用并分配的undo页(事务提交后,其所使用的undolog不再需要)

内存池

  • 缓冲池(一块内存区域)
    1)InnoDB基于磁盘,将记录按照页的方式进行管理(引入缓冲池提高性能)
    2)读取页:先从缓冲池获取,缓冲池没有,才会从磁盘获取
    3)修改页:先写redo日志缓冲,再修改缓冲池中的页,然后以一定的频率刷新到磁盘(Checkpoint机制),在没有刷新到磁盘之前,该页被称为脏页
    4) innodb_buffer_pool_size:设置大小
    5)存放索引页、数据页、自适应hash索引和lock信息
    6)缓冲池可以配置多个(innodb_buffer_pool_instances),每个页根据hash值平均分配到不同的缓冲池实例中,用于减少数据库内部资源竞争
  • LRU List
    将最新的页放在队列前端,最近最少使用的放在尾端,当缓冲池不够用时,将尾端的页删除出缓冲池(如果此页是脏页,会先刷新到磁盘)。innodb采用的是midpoint技术进行LRU
  • Flush List
    脏页列表
  • 重做日志redolog缓冲
    1)为了防止脏页在刷新到磁盘时宕机,必须先redolog,再修改页
    2)数据库发生宕机时,通过redolog完成数据的恢复(ACID-D持久性)
    3)默认大小8M,通过innodb_log_buffer_size
    4)何时将redolog缓冲刷新到redolog文件:①master将redolog缓冲每隔1s刷新到redolog文件中②事务提交③redolog缓冲池剩余空间小于1/2
  • Checkpoint
    1)缓冲池不够用时,将脏页刷新到磁盘
    2)数据库宕机时,只需要重做Checkpoint之后的日志,缩短数据库的恢复时间
    3)redolog不可用时,将脏页刷新到磁盘

InnoDB逻辑存储结构

innodb逻辑结构.png

表空间

  • 默认情况下,只有一个表空间ibdata1,所有数据存放在这个空间内
  • 如果启用了innodb_file_per_table,则每张表内的数据可以单独放到一个表空间内,每个表空间只存放数据、索引和InsertBuffer Bitmap页,其余还在ibdata1

Segment段(InnoDB引擎自己控制)

  • 数据段:B+ tree的叶子节点
  • 索引段:B+ tree的非叶子节点
  • 回滚段

Extent区

  • 每个区的大小为1M,一个区一共有64个连续的页(区的大小不可调节)

  • InnoDB磁盘管理的最小单位
  • 默认每个页大小为16KB,可以通过innodb_page_size来设置(4/8/16K)
  • 每个页最多存放7992行数据

Row行

索引

hash索引

  • 定位数据只需要一次查找,时间复杂度O(1)
  • 只可用于等值查询,不可用于范围查询
  • 自适应hash索引:InnoDB会自动的根据访问频率和模式来自动的为某些热点页建立hash索引,默认是开启的,不能人为干预是否在表中生成哈希索引。

B+树索引

  • B+索引在数据库中有一个特点是高扇出性
  • 树的高度一般为2-4层,需要2-4次查询(100w和1000w行数据,如果B+ tree都是3层,那么查询效率是一样的)
  • B+树索引并不能找到一个给定键值的具体行。B+树索引能找到的只是被查找数据行所在的页。然后数据库通过把页读入到内存,再在内存中进行查找,最后得到要查找的数据。
  • B+树索引可以分为聚集索引(clustered index)和辅助索引(secondary index),但是不管是聚集还是辅助索引,其内部都是B+树的,即高度平衡的,叶子节点存放着所有的数据。聚集索引和辅助索引不同的是,叶子节点存放的是否是一整行的信息。

聚集索引

  • 聚集索引按照每张表的主键构造一棵B+树。
  • 叶子节点中存放的是整张表的行记录数据,叶子节点也成称为数据页。
  • 索引组织表中数据也是索引的一部分。每个数据页通过一个双向链表来进行链接。聚集索引能够特别快的访问针对范围值的查询。
聚集索引图.png
  • 上图中,根节点部分的Key:80000001代表主键为1。Pointer:0004代表指向数据页的页号(即第4页)。数据页节点的的PageOffset:0004代表第4页,其中存储的数据是完整的每一行。

辅助索引

  • 叶子节点存放的也是行记录数据所在的页,但还是页中存放的不是完整的行,而是仅仅是一对key-value和一个指针,该指针指向相应行数据的聚集索引的主。
  • 假设辅助索引树高3层,聚集索引树为3层,那么根据辅助索引查找数据,需要先经过3次IO找到主键,再经过3次IO找到行做在的数据页
  • 针对辅助索引的插入和更新操作:辅助索引页如果在缓冲池中,则插入;若不在,则点放到InsertBuffer对象中,之后在以一定的平率进行InsertBuffer和辅助索引页子节点的合并
辅助锁索引图.png
  • 图中,idx_c表示对第c列做了索引;idx_c中的Key:7fffffff代表c列的一个值,其实是-1;idx_c中的Pointer:80000001代表该行的主键是80000001,即1;下面的就是聚集索引部分。

联合索引
覆盖索引

InnoDB存储引擎中的锁

锁的类型:

InnoDB存储引擎实现了如下两种标准的行级锁

  • 共享锁:S lock,事务T1获取了r行的S锁,事务T2也可以获取r行的S锁
  • 排他锁:X lock,事务T1获取了r行的S锁,事务T2就不能获取r行的X锁;事务T1获取了r行的X锁,事务T2就不能获取r行的X/S锁
  • InnoDB存储引擎支持多粒度锁定,这种锁定允许在行级上的锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,InnoDB存储引擎支持一种额外的锁方式,即意向锁。
  • 意向锁是表级别的锁,其设计目的主要是为了在一个事务中揭示下一行将被请求的锁的类型:
    1)意向共享锁(IS Lock),事务想要获得一个表中某几行的共享锁。
    2)意向排它锁(IX Lock),事务想要获得一个表中某几行的排它锁。
    因为InnoDB支持的是行级别的锁,所以意向锁其实不会阻塞除全表扫以外的任何请求。

一致性的非锁定读操作

  • 通过行多版本并发控制(MVCC)的方式来读取当前执行时间数据库中行的数据。如果读取的行正在执行DELETE、UPDATE操作,这时读取操作不会因此等待行上的锁释放,相反,存储引擎会去读取一个快照数据。
    快照数据是指该行之前版本的数据,该实现是通过Undo段来实现。而Undo用来在事务中回滚数据,因而快照数据本身是没有额外的开销。此外,读取快照数据是不必要上锁的,因为没有必要对历史的数据进行修改。
  • 在Repeatable Read事务隔离级别下,对于快照数据,总是读取事务开始时的行数据版本。
  • 在Read Comitted事务隔离级别下,对于快照数据,总是读取被锁定行的最新一份快照数据。
  • 对于Read Commited的事务隔离级别而言,其实违反了事务的隔离性。
    锁定读操作:
  • SELECT…FOR UPDATE 对读取的行记录加一个X锁。其他事务想在这些行上加任何锁都会被阻塞。
  • SELECT…LOCK IN SHARE MODE 对读取的行记录加一个S锁。其他事务可以向锁定的记录加S锁,但是对于加X锁,则会被阻塞。

锁的算法

InnoDB存储引擎有3种行锁

  • Record Lock:单个行记录上的锁,锁定的对象是索引,而不是数据。
  • Gap Lock:间隙锁,锁定一个范围的索引,但不包含记录本身
  • Next-Key Lock: Gap Lock + Record Lock,锁定一个范围的索引,并且锁定记录本身。
  • Record Lock总是会锁住索引记录,如果InnoDB存储引擎建立的时候没有设置任何一个索引,这时InnoDB存储引擎会使用隐式的主键来进行锁定。
  • 如:SELECT * FROM t WHERE a < 6 lock in share mode,该语句会锁定(-oo, 6)这个数值区间的所有数值。

解决幻读问题:

  • 幻读:同一事务下,连续执行两次同样的SQL语句可能导致不同的结果。第二次SQL语句可能会返回之前不存在的行。
  • 可重读读下,InnoDB存储引擎采用Next-Key Locking机制来避免幻读。
  • 假如表t由1,2,5三个值组成, where a > 2 ,被锁住的不仅是5这个值,而是(2,+∞)这个范围加了X锁
  • 在读提交级别下,仅采用Record Lock

锁问题

脏读:即一个事务可以读到另一个事务中未提交的数据,违反了数据库的隔离性。发生条件:READ UNCOMMITED,这个隔离级别在Mysql中不使用。
不可重复读

  • 一个事务两次读同一数据,结果不一样。(另一个事务修改了改数据并提交)

  • 不可重复读和脏读的区别是:脏读是读到未提交的数据;而不可重复读读到的确实是已经提交的数据,但是其违反了数据库事务一致性的要求。

  • InnoDB的默认事务隔离级别是READ REPEATABLE,采用Next-Key Lock算法,解决了不可重复读(幻读)问题。
    在Next-Key Lock 算法下,不仅仅是锁住扫描到的索引,而且还锁住这些索引覆盖的范围(gap)。因此对于这个范围内的插入都是不允许的。

死锁

  • 死锁是指两个或两个以上的事务在执行过程中,因争夺锁资源而造成的一种相互等待的现象。
  • 超时机制:当一个事务等待超时,则对它进行回滚。但是有可能回滚的这个事务的时间要比另一个事务要多。(就是还不如回滚另一个没超时的)
  • InnoDB采用wait-for graph(等待图)的方式来进行死锁检测。

锁升级

  • 指将当前锁的粒度降低,比如行锁升级为一个页锁,或者将页锁升级为表锁。
  • InnoDB不存在锁升级的问题。其根据每个事务访问的每个页对锁进行管理,采用的是位图的方式。不管事务锁住页中的一个记录还是多个记录,其开销是一样的。

事务

事务定义

事务:事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚
ACID

  • Atomicity 原子性: 数据库事务是不可分割的工作单位, 要么都做, 要么都不做.
  • Consistency 一致性: 事务不会破坏事务的完整性约束. 事务将数据库从一个一致状态转变为另一个一致状态
  • Isolation 隔离性: 事务之间相互分离, 在提交之前相互不可见.
  • Durability 持久性: 事务一旦提交, 其产生的效果就是永久的

事务的实现

  • 事务的隔离性由锁实现
  • 原子性,一致性,持久性 由 redo log / undo log 实现
  • undo log 保证事务的一致性, 逻辑日志, 根据每行记录进行记录,帮助事务回滚及MVCC
  • redo log(重做日志): 保证事务的原子性和持久性, 物理日志

redo log

  • 由两部分组成:内存中的重做日志缓冲,易失的。重做日志文件,持久的。事务提交时,必须将事务的的所有日志写入重做日志进行持久化。

undo log

  • 记录的是 SQL, undo 之后底层物理文件格式可能会改变。当前事务通过undo读取之前的行版本信息,来实现非锁定读。undo log 会产生redo log,因为undo log也需要持久性的保护。

purge

  • 删除并没有删除原数据,只是delete flag置为1
  • 若该行记录已经不被其他事务引用,则purge完成真正的删除

你可能感兴趣的:(MySQL技术内幕 :InnoDB存储引擎(第二版))