MySQL InnoDB存储引擎要点

支持功能:

  • 行级锁

    • 四种锁:S(共享锁)、X(排他锁)、IS(意向共享锁)、IX(意向排他锁),前两种是行锁,后两种是表锁

    • 意向锁是为了协调行锁和表锁的关系,加行锁前,必须先加相应意向锁,意向锁间相互兼容,意向锁和表锁有互斥关系

    • 普通select不加锁,当前读select加共享锁,insert、update、delete加排他锁

    • 三种行锁:Record Lock(记录锁),Gap Lock(间隙锁),Next-Key Lock(前两种结合)

    • 行锁针对的是索引,唯一索引优化为Record,其他为Next-Key,注意死锁问题

  • 事务

    • 四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),隔离性通过MVCC和锁实现,原子性、持久性分别通过undo log(撤消日志)和redo log(重做日志)实现,一致性依赖前三者,由应用层保证满足约束条件。

    • 通过redo log和undo log保证,前者写入文件,后者写入共享表空间,LSN(日志序列号)机制

存储结构:

  • 表空间(tablespace)

    • 共享表空间,存储undo信息、插入缓冲索引也、系统事务信息、两次写缓冲等

    • 单独表空间(可选),存储数据、索引和插入缓冲Bitmap页

    • 索引组织的,数据即索引,索引即数据,B+树结构

  • 段(segment)

    • 数据段

    • 索引段

    • undo段

  • 区(extent)

  • 页(page)

    • 数据页(B+ Tree Node)

    • undo页

    • 系统页

    • 事务数据页

    • 插入缓冲位图页

    • 插入缓冲空闲列表页

    • 未压缩的二进制大对象页

    • 压缩的二进制大对象页

  • 行(row)

    • 三个隐藏字段:隐藏的ID(可能)、6字节的事务ID(DB_TRX_ID)、 7字节的回滚指针(DB_ROLL_PTR)
  • 实现上每个页对应一个B+ Tree Node,页大小和行大小决定了每个节点记录数

  • 默认每个段4个区,每区64个页,每页16K,每行记录超出则行外存储至二进制大对象页

  • 不考虑每页页头等数据,简化计算,非叶子节点每个条目至少包含一个8字节key和6字节指向子节点的指针,则每个非叶子节点最大可以有16K/14个条目;假设每条数据记录大小为1K,每个叶子节点页面可以存储16K/1K=16行数据记录;一个三层B+数最大可以存储(16K/14) x (16K/14) x (16K/1k) = 2191万行数据记录

缓冲池:

  • 普通缓冲池

    • 数据页

    • 索引页

    • 插入缓冲,辅助索引插入优化

    • 自适应哈希索引,索引效率优化

    • 锁信息

    • 数据字典信息

  • 重做日志缓冲池

  • 额外缓冲池

    • 缓冲池的数据结构本身、帧缓冲、缓冲控制对象等
  • 两次写,LRU

索引 :

  • 聚簇索引

    • key为索引值,value为行数据
  • 辅助索引

    • key为索引值,value为聚簇索引键
  • 采用B+树,非叶节点无数据,2到4层高度,减少磁盘IO次数

线程:

  • Master Thread

    • 合并插入缓冲、脏页刷新(已单独)、undo页回收(已单独)等
  • IO Thread

    • 读(页)线程、写(页)线程、插入缓冲线程、日志线程
  • Purge Thread

    • undo页回收
  • Page Cleaner Thread

    • 脏页刷新

MVCC:

  • 事务以排他锁的形式修改原始数据

  • 把修改前的数据存放于undo log,通过回滚指针与主数据关联,同时会记录下该行数据对应的创建版本号,即生成该数据行的事务ID

  • 修改成功(commit)什么都不做,失败则恢复undo log中的数据(rollback)

  • 查询操作时,拿到当前ReadView,即事务快照,是当前时间点系统内活跃的(未提交的)事务列表,对比要查询的数据最新及至历史版本,可得可见版本的数据行

InnoDB的操作可以分为当前读(current read)和快照读(snapshot read):

  • 快照读(snapshot read)

    • 普通select操作(不包括 select … for share, select … for update)
  • 当前读(current read)

    • select … for share

    • select … for update

    • insert

    • update

    • delete

快照读是通过MVCC来实现的,视隔离级别不同,采用不同的ReadView生成策略

  • 已提交读:每次读操作都会生成一个新的ReadView

  • 可重复读:事务第一次进行读操作时才会生成一个ReadView

当前读是通过加锁来实现的,视隔离级别不同,采用不同的加锁策略

  • 已提交读:写加Record排它锁,事务结束释放;读加Record共享锁,事务结束释放

  • 可重复读:写加Next-Key排它锁,事务结束释放;读加Next-Key共享锁,事务结束释放

未提交读:无MVCC,无锁

可串行化:取消快照读,全为当前读

隔离级别不同,索引类型不同,加锁策略也不同,比如同样是当前读,where key = 1:

  • 聚簇索引:RC和RR,数据记录加Record Lock

  • 唯一索引:RC和RR,索引记录和数据记录均加Record Lock

  • 非唯一索引:RC,索引记录和数据记录加Record Lock;RR,索引记录加Record Lock + Gap Lock,数据记录加Record Lock

  • 非索引:RC,所有数据记录加Record Lock;RR,所有数据记录加Record Lock + Gap Lock;优化,不满足条件的记录释放锁