隔离级别

MySQL 隔离级别是比较基础的知识,也是面试经常考察的知识点。

事务隔离级别

事务隔离级别包括:读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(serializable )。

读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。 读提交:一个事务提交之后,它做的变更才会被其他事务看到。 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。 串行化:对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

不可重复读、幻读、脏读

脏读:当一个事务正在访问数据,并且对数据进行了修改,而这种修改尚未提交,此时,另一个事务也访问这个数据,然后使用了这个数据。

不可重复读:前后多次读取一行,数据内容不一致,针对其他事务的update和delete操作。为了解决这个问题,使用行共享锁,锁定到事务结束(也就是可重复读级别,当然MySQL使用MVCC在读提交级别就解决了这个问题)

幻读:当同一个查询在不同时间生成不同的行集合时就是出现了幻读,针对的是其他事务的insert操作,为了解决这个问题,锁定整个表到事务结束(也就是串行化级别,当然MySQL在RR级别使用间隙锁就可以解决这个问题)

隔离级别 脏读 不可重复读 幻读
读未提交 🙋 🙋 🙋
读已提交 x 🙋 🙋
可重复度 x x 🙋
串行化 x x x

实现方式

  1. LBCC

Lock Based Concurrency,要保证前后两次读取数据一致,那么读取数据的时候,锁定要操作的数据,不允许其他的事务修改。而大多数应用都是读多写少的,这样会极大地影响操作数据的效率。

  1. MVCC

Multi-Version Concurrency Control,一种多版本并发控制机制,基于数据的多版本去进行控制,无锁化的设计方案。

重点说说 MVCC

MVCC

MVCC的实现原理主要是依赖 3个隐藏字段、 undo log 和 Read View。

隐藏字段

InnoDB 内部为每一行数据添加了三个字段:

  • 一个 6 字节DB_TRX_ID字段:插入或更新行的最后一个事务的事务标识符。(删除在内部被视为更新,其中行中的特殊位设置为将其标记为已删除)
  • 一个 7 字节的DB_ROLL_PTR字段:滚动指针,指向 undo log 记录。如果行已更新,undo log记录包含在更新之前重建行内容所需的信息。
  • 一个 6 字节的DB_ROW_ID字段:包含一个行 ID,行 ID 随着新行的插入而单调增加。如果 InnoDB 自动生成聚集索引,则索引包含这个行 ID 值。否则,该 DB_ROW_ID 列不会出现在任何索引中。

Read View

Read View 在事务执行快照读的时候生成。需要注意的是,“读已提交”级别下,会在 每个SQL开始执行的时候 创建Read View。“可重复读”级别下,会在 每个事务开始的时候创建Read View。“串行化”级别下,直接加锁避免并发问题。“读未提及”级别下,没有Read View。

Read View 遵循一个可见性算法,主要是将要被修改的数据的最新记录中的DB_TRX_ID(即当前事务ID)取出来,与系统当前其他活跃事务的ID去对比(由Read View维护),如果DB_TRX_ID跟Read View的属性做比较,不符合可见性,那就通过DB_ROLL_PTR回滚指针去取出Undo Log中的DB_TRX_ID再比较,即遍历链表的DB_TRX_ID(从链首到链尾,即从最近的一次修改查起),直到找到满足特定条件的DB_TRX_ID, 那么这个DB_TRX_ID所在的旧记录就是当前事务能看见的最新老版本。

Read View 的三个全局属性:

  • 用于维护 Read View 生成时刻系统正活跃的事务 ID 列表
  • 活跃列表中事务 ID 最小的 ID
  • Read View 生成时刻系统尚未分配的下一个事务 ID ,也就是目前已出现过的最大的事务 ID + 1

可见性判断

  • DB_TRX_ID 比活跃的最小事务id还小时,说明当前行事务对该记录的修改已经提交,因为当前事务id比活跃的最小事务id还小,不在活跃的事务之中,也就意味着该事务已经提交或回滚。也就是在生成Read View之前,事务已经提交。
  • DB_TRX_ID 不小于稍微分配的事务id, 说明修改该行的事务是生成该Read View之后出现的事务,这时,应该是不可见的,一个事务怎么可以看到后面新来事务做的修改了。
  • 判断 DB_TRX_ID 是否在活跃事务之中,如果在,说明该修改是其他事务未提交的修改,应该是不可见的,如果可见就是脏读了,如果不在活跃事务之中,说明在生成Read View之前,该事务的修改就已提交,与第一个判断逻辑类似,事务2是可以查到这条记录的。

这一块有点抽象,可以多读几次,理解一下。

感谢您的阅读,这就是 MySQL 的隔离级别与实现啦,面试很有用哦!

参考

关注和赞赏都是对小欧莫大的支持! 🤝 🤝 🤝
公众号