MVCC与BufferPool缓存机制

快照读不加锁的简单SELECT都属于快照读,当前读即读取最新数据而不是历史数据,加锁的SELECT或对数据进行增删改都会进行当前读。

MVCC多版本并发控制机制

MySQL在可重复读隔离级别下同样的SQL查询语句在一个事务里多次执行查询结果相同,就算其它事务对数据有修改也不会影响当前事务SQL语句的查询结果。

该隔离性是靠MVCC(Multi-Version Concurrency Control)机制来保证的,对同一行数据的默认不通过加锁互斥来保证隔离性,避免频繁加锁互斥,而在串行化隔离级别为了保证较高的隔离性是通过将所有操作加锁互斥来实现的。MySQL在读已提交可重复读隔离级别下都实现了MVCC机制

undo日志版本链与read view机制详解

undo日志版本链是指同一行数据被多个事务依次修改过后,在每个事务修改完后,MySQL会保留修改前的数据undo回滚日志,且用两个隐藏字段trx_idroll_pointer把这些undo日志串联起来形成一个历史记录版本链

每开启一个日志都会从数据库中获得一个事务idtrx_id,且trx_id自增的可判断事务的时间顺序roll_pointer回滚指针,指向undo日志中的上一个版本。

可重复读隔离级别,当事务开启,执行第一条查询SQL时会生成当前事务的一致性视图read-view,该视图在事务结束之前都不会变化,该视图是由执行查询时所有未提交事务id组成的数组,数组里最小的事务idmin_id,已创建的最大事务id为max_id,事务里的任何SQL查询结果需要从对应版本链里的最新数据开始逐条跟read-view做对比从而得到最终的快照结果

对于读已提交隔离级别在每次执行查询SQL时都会重新生成read-view,其他逻辑跟可重复读一致。

版本链比对规则

若row的trx_id落在绿色部分trx_id<min_id,表示该版本是已提交的事务生成的,则该数据是可见的;

若row的trx_id落在红色部分trx_id>max_id,表示该版本是由将来启动的事务生成的,则该数据是不可见的,若row的trx_id就是当前自己的事务是可见的

若row的trx_id落在黄色部分min_id <=trx_id<= max_id,则包括两种情况:

  • 若row的trx_id在视图数组中,表示该版本是由还没提交的事务生成的,则该数据不可见,若row的trx_id是当前自己的事务可见
  • 若row的trx_id不在视图数组中,表示该版本是已经提交了的事务生成的,则该数据可见

删除可认为是update的特殊情况,会将版本链上最新的数据复制一份,然后trx_id修改成删除操作的trx_id,同时在该条记录的头信息record header里的deleted_flag标记位写上true,表示当前记录已经被删除,在查询时按照上面的规则查到对应的记录若delete_flag标记位为true,意味着记录已被删除,则不返回数据。

beginstart transaction命令并不是一个事务的起点,在执行到它们之后的第一个修改InnoDB表的语句,事务才真正启动才会向MySQL申请事务id,MySQL内部是严格按照事务的启动顺序来分配事务id的

MVCC机制的实现就是通过read-view机制undo版本链比对机制,使得不同事务根据数据版本链对比规则读取同一条数据在版本链上的不同版本数据

BufferPool缓存机制

数据库的增删改查都是直接操作BufferPool,BufferPool一般设置为机器内存的60%左右,执行SQL执行流程如下:

  1. 加载缓存数据记录所在的整页数据到BufferPool
  2. 写入更新数据旧值到undo日志中,便于回滚恢复BufferPool中的缓存数据
  3. 更新BufferPool中缓存的数据
  4. redo日志,首先写到Redo Log Buffer
  5. 准备提交事务,redo日志写入磁盘redo日志文件
  6. 准备提交事务,binlog日志写入磁盘binlog日志文件中,用来恢复数据库磁盘中的数据
  7. 写入commit标记到redo日志文件中,事务提交完成。该标记为了保证事务提交后redobinlog数据一致
  8. 以page页为单位将数据随机写入磁盘

Mysql执行过程与BufferPool缓存机制

若来一个请求就直接对磁盘文件进行随机读写,然后更新磁盘文件里的数据性能可能相当差。因为磁盘随机读写的性能非常差,所以直接更新磁盘文件是不能让数据库抗住很高并发的。

MySQL这套机制看起来复杂,但它可以保证每个更新请求都是更新内存BufferPool,然后顺序写日志文件,同时还能保证各种异常情况下的数据一致性。若事务提交成功,BufferPool中的数据还没来得及写入磁盘,此时若系统宕机了,可以redo日志中的数据恢复BufferPool中的缓存数据

更新内存的性能是极高的,然后顺序写磁盘上的日志文件的性能也是非常高的,要远高于随机读写磁盘文件。正是通过这套机制,才能让MySQL数据库在较高配置的机器上每秒可以抗下几干的读写请求。

BufferPool默认为128M,MySQL的数据是以文件页16K为单位加载到BufferPool,故默认BufferPool可放8192个文件页,且BufferPool分冷热数据,一般热数据占八分之五冷数据占八分之三,且通过升级版的LRU链表维护。若对于一个大表进行全表扫描时,可能很快就把缓存页数据占满了,故MySQL会判断超过1s再次被访问的数据才会放入热数据区域,否则使用冷数据区域。

BufferPool通过空闲列表维护了空闲页free链表,当数据被查询到BufferPool中会判断空闲页,写到固定的位置,且若发生数据更新则会产生脏页,这些脏页也会被维护到一个Flush链表中。且通过基节点维护了链表信息。

redo log记录的是某一页的哪个地址开始的数据被修改redo log有两个日志文件,会滚动写入即当一个文件写满了就会写另一个文件,且有一个检查点的概念,若覆盖写入时发现了脏数据即BufferPool中缓存页数据被修改过,则会先触发刷盘操作FlushList中的文件页的数据批量刷到磁盘中,则redo log文件配置越小刷盘越频繁性能就越受影响,但redo log配置的很大可能导致启动缓慢redo log可通过配置值从而配置其持久化到redo log文件的时机:

  • 0:事务提交时,不立即对redo log进行持久化,交给后台线程异步去完成
  • 1:默认值,事务提交时,立即把redo log进行持久化
  • 2:事务提交时,立即将redo log写到操作系统的缓冲区,并不会直接将redo log进行持久化,若数据库挂了,但操作系统没有挂,则事务的持久性还可以保持

Change Buffer存储的是索引的更新信息,当执行SELECT时加载索引页时,会将索引页与Change Buffer中的缓存数据融合。