目录

0.事务隔离级别

1.MVCC

2.版本控制算法

2.1 select 查询数据伪代码

2.2 可见性算法


0.事务隔离级别

我们都知道mysql innodb引擎支持事务,为了兼顾事务并发时之间的隔离性(ACID)和性能,提供了以下4种隔离级别及其对应的问题:

隔离级别

问题&优势

问题解释

读未提交

脏、不可重复、幻读

性能最高

脏读:事务A读取事务B改写但未提交的数据,如果回滚,则读到无效数据。

不可重复读:事务A多次读取同一数据返回的结果不同 (update)

幻读:事务A读取几行记录后,事务B插入(insert)一些记录。后来的查询中,事务A发现有些原来没有的记录。

读已提交

不可重复、幻读

性能较高

可重复读REPEATABLE-READ

幻读

性能平衡 默认选择

串行

null

依次执行,不会并发 性能差

那这4中隔离级别是如何实现的呢?

很显然读未提交,innodb引擎啥也没干,所有事务都是读取数据最新的记录

串行化也很好理解,就是不允许并发了,加锁互斥访问即可,同一数据同时刻仅能被一个事务修改,也不会有以上问题

中间的两种读已提交和可重复读是用的MVCC多版本并发控制来实现的,在保证并发的数据安全问题,也提升了读写线程的活跃性。具体的下面详解~

1.MVCC

  • MVCC (Mutil-Version Concurrency Control,多版本并发控制),多个事务并发进行时,对于每行每个事务有自己的Read View版本,这样就无需给行加读锁,就不会阻塞写操作,以获得更大的并发度
  • 多版本,一条记录存在这多个版本,某时刻最新的版本存储在数据页上,历史版本存储在undo log回滚段中,每行数据和历史版本有隐藏列DATA_TRX_ID和DATA_ROLL_PTR和旧数据。

DATA_TRX_ID表示更新这个版本的事务id(版本号);

DATA_ROLL_PTR指向上个版本,构成版本链

 

mysql隔离级别详解 mysql 隔离级别实现原理_mysql

  • trx_id事务id,每个事务begin开始时获取的全局唯一的连续递增的序列号(也称为版本号)
  • Read View 视图,每个事务(RR,可重复读)或每条语句(RC,读已提交)会在开始时创建的结构体,用于版本可见性控制。包含

creator_trx_id

当前事务id

up_limit_id

活跃的最小事务id

low_limit_id

活跃的最大事务id

trx_ids

活跃事务列表

2.版本控制算法

mysql隔离级别详解 mysql 隔离级别实现原理_mysql隔离级别详解_02

  • time1:开启事务,扫描事务链表trx_sys(当前活跃的事务),选取事务id构建Read View

creator_trx_id

当前事务id

up_limit_id

活跃的最小事务id

low_limit_id

活跃的最大事务id

trx_ids

活跃事务列表

  • time 2: select 查询数据 ,从数据页的数据行(版本链的head版本)开始遍历,如果该版本可见(比较版本号和ReadView)返回,如果不可见走到下一跳(上个版本)。
  • times:可见? 比较版本的DATA_TRX_ID和up_limit_id、low_limit_id,判断DATA_TRX_ID在不在trx_ids列表中,得出结论当前事务能否看见这个版本。

2.1 select 查询数据伪代码

select(row, readView){
    node = row //数据页行记录
    while(node.roll_ptr!=null 
            && canSee(node.trx_id, readView)) { // 顺着版本链找到最新的可见的版本
        node = *roll_ptr
    }
    return node.oldVal // 版本值
}

2.2 可见性算法

如下图,每个箭头表示一个事务的开始和提交,按照时间顺序从上到下

mysql隔离级别详解 mysql 隔离级别实现原理_mysql隔离级别详解_03

  1. 事务A在当前事务R开始之前 提交可见
  2. 事务B在当前事务R开始之后才 开始不可见
  3. 事务C在当前事务R开始之前开始,但在检查可见之前提交,根据事务隔离级别有不同的可见性
  1. RR 可重复读,不可见
  2. RC 读已提交, 可见
canSee(trx_id, readView){
    if(trx_id < readView.up_limit_id) //这些数据在事务创建id的时都已经提交
        return true; 	
    if(trx_id >= readView.low_limit_id) //该事务在当前事务开始后开始
        return false
    if(up_limit_id<trx_id<low_limit_id){
        if(隔离级别==可重复读RR) 
            return false
        if(隔离级别==读已提交RC) {
            if(read_view->trx_ids.contains(trx_id))
                return false //修改当前版本的事务还活跃,未提交,不可见
            else{
                return true // 该事务已提交,RC可读
            }
        }
    }
}