MySQL事务隔离级别以及MVCC原理

0 阅读5分钟

MySQL数据库事务的隔离级别

  • 读未提交(READ-UNCOMMITTED)
  • 读已提交(READ-COMMITTED):简称 RC
  • 可重复读(REPEATABLE-READ):简称 RR
  • 串行化(SERIALIZABLE)

不同隔离级别下可能出现的问题

  • 脏读:读到了未提交的数据

balance初始值为900,执行过程中,事务B读取到了事务A未提交的数据

image.png

  • 不可重复读:两次读取的数据不一致

事务A第一次读取balance为800,由于事务B修改了balance并提交,事务A在进行第二次读取时balance为1000,导致同一个事务中两次读取结果不同

image.png

  • 幻读:这类问题一般针对整张表,使用聚合函数进行统计时,两次统计数据不一致

事务A第一次读取表中数据一共5条,事务B插入数据后提交,事务A再次读取时发现数据有10条,两次记录不一样导致出现了幻读,这可能和不可重复读一样有点难理解,看下面另一个说明

image.png 这里我们结合业务需求来理解: A事务的业务需求是判断用户是否存在,不存在就创建用户(自动注册场景),A一开始查询到用户不存在,这时候想要去创建用户,但此时CPU执行权被事务B先抢占了,导致id为5的记录插入了数据库,当事务A再去执行插入操作,会出现主键已存在的冲突导致数据库抛出异常,业务捕获异常后进行了其他操作,用户既没有正常登录也没有注册成功,这种情况称之为幻读

这里的没有注册成功是针对事务A中来看的,因为在事务A所属的线程来说,数据库是抛出了异常的,业务层面捕获异常并返回了错误信息

image.png

MVCC介绍

MVCC(Multi-Version Concurrency Control)多版本并发控制,在不加锁的情况下,解决并发事务下保证事务的一致性和隔离性,提高数据库并发性能

MVCC实现原理

隐藏列

在MySQL数据库表中,每一行数据都维护着三个隐藏列,db_row_id(隐藏的自增主键),db_trx_id(当前行数据所属的事务ID),db_roll_ptr(指向该记录修改前上一条记录的指针,出现问题用于回滚)

image.png

undo log

undo日志表,主要用于事务的回滚操作和MVCC的实现,在undo日志表中,数据之间由指针进行串联,通过Read View可以进行对应数据的查找

image.png

Read View

Read View中存储了几个关键信息,m_creator_trx_id(当前事务ID),m_ids(所有未提交的事务ID),m_up_limit_id(所有未提交事务中的最小ID),m_low_limit_id(系统中尚未分配的最小事务ID/未来事务ID)

m_up_limit_id就是从m_ids取出最小的, m_low_limit_id则是从m_ids中取出最大的+1

image.png

实现原理

在查询时会MySQL会创建一个Read View,通过Read View结合4条判断在undo log中哪个版本可以使用,undo log日志中最新的数据在第一个,因此匹配不成功会一直往下匹配,4条判断规则:

  1. undo logdb_trx_idcreator_trx_id相等,表示当前版本是在当前事务下执行的,那在同一个事务中,数据是可以直接访问的
  2. undo logdb_trx_id小于当前所有未提交事务的最小ID(mid_up_limit_id),即这个版本在本次所有事务执行前已经提交,可以访问
  3. undo logdb_trx_id大于等于未来事务ID(m_low_limit_id),不可以访问
  4. undo logdb_trx_id在所有未提交事务ID之间,即mid_up_limit_id<= trx_id < m_low_limit_id之间则不可以访问,因为在此区间内的事务都未提交,反之代表db_trx_id在当前事务创建Read View前就已经提交了,可以访问

总结:

  • db_trx_id == creator_trx_id => true
  • db_trx_id < mid_up_limit_id => true
  • db_trx_id >= m_low_limit_id => false
  • mid_up_limit_id<= trx_id < m_low_limit_id => false

示例

场景:

现在有事务A(ID为13),事务B(ID为12),事务C(ID为15)三个事务,对balance进行查询操作,假设事务A先执行了查询操作 事务A在执行select操作前会创建一个Read View,如下:

image.png undo log如下:

image.png

执行流程:

  • 1、根据当前的事务ID为13,在undo log中进行记录匹配,先匹配第一条数据balance为1000,所属的事务ID为12
    • 12 != 13,下一条规则
    • 12 < 12不成立,下一条规则
    • 13 > 16不成立,下一条规则
    • 12 <= 13 < 16成立,不可以访问,匹配下一条数据
  • 2、匹配第二条数据balance为800,所属的事务ID为10
    • 10 != 13,下一条规则
    • 10 < 13成立,当前记录在事务执行前已提交,返回

读已提交和可重复读区别

事务A在一次事务中执行了两次查询,在可重复读场景下,Read View只会在第一次查询时创建,但是在读已提交场景下,每一次执行查询操作都会创建Read View

image.png