Skip to content

事务的4个特性 AICD

  • 原子性(A): 一个事务中的操作要么全部成功,要么全部失败
  • 隔离性(I): 两个事务之间互不影响
  • 一致性(C): 通过数据库字段约束以及业务程序实现符合现实世界的约束
  • 持久性(D): 一旦事务执行成功,那么结果就应该永久保存

redo日志

前面的内容我们知道了Mysql修改数据的时候都是现在内存中修改,然后在刷新到磁盘上面,在刷新之前系统可能出现了故障导致内存中的数据丢失了,这该怎么办呢? 简单粗暴的方法就是每次事务提交的时候就刷新数据到磁盘,这样也可以解决,但是存在两个问题:

  1. MySql与磁盘交互的最小单位是一个页,至少要刷新一个页面,太耗性能
  2. 随机IO刷新会很慢

MySQL的做法是在事务提交的时候记录 哪个表空间中的 哪个页面及页面偏移量的数据需要修改成什么,这就是redo日志。如果系统出现了崩溃,在恢复后需要从redo日志中 重新执行更新逻辑

  • redo日志占用的空间较小,只记录表空间ID,页号,偏移量以及需要修改的数据
  • redo日志是顺序写入到磁盘

事务的隔离级别

事务在并发执行的时候会遇到一些问题

  • 脏写: 一个事务修改了另一个未提交事务的数据
  • 脏读: 一个事务读取了另一个未提交事务的修改过的数据
  • 不可重复读: 一个事务修改了另一个未提交事务读取的数据,导致未提交的事务两次读取的结果不一致
  • 幻读: 一个事务根据某些条件查询出来了一些记录,另一个事务删除或插入了一条符合查询条件的数据。

为了解决这些问题,SQL标准中定义了4中隔离级别

  • read uncommitted: 解决脏写,可能出现脏读、不可重复读、幻读
  • read committed: 解决脏写、脏读,可能出现不可重复读、幻读
  • repeatable read: 解决脏写、脏读、不可重复读,可能出现幻读
  • serializable: 解决所有问题

MySQL的默认隔离级别是 repeatable read.

设置事务的隔离级别命令:

sql
set [global|session] transaction level [read uncommitted|read committed|repeatable read|serializable]

MVCC原理

innodb引擎的表,它的聚簇索引记录中都包含了下面两个必要的隐藏列:

  • trx_id: 一个事务每次对某条记录进行修改的时候,都会把该事务的事务id设置给trx_id
  • roll_pointer: 每次对聚簇索引的记录进行修改的时候,都需要把旧的版本写入到undo日志中,这个roll_pointer就是指向这条旧的数据。

每次对记录进行一次改动,都会记录一条undo日志,每条undo日志都会有trx_id、roll_pointer两个字段,roll_pointer指向更早的记录,这种机制就是多版本并发控制(MVCC)

MySQL如何实现4种隔离级别

read uncommitted: 由于允许读取到未提及事务的数据,所以事务直接读取MVCC的最新数据即可。

serializable: 这个级别就是要求所有事务串行执行,所以MySQL采用的是加锁的方式实现

read committed / repeatable read: 这两个隔离级别都不允许读取到未提交事务的数据,核心问题就是需要判断MVCC版本链中哪些数据是对当前事务可见,所以MySQL又引入了ReadView(一致性视图)

ReadView 主要4个字段:

  • m_ids: 生成ReadView的时候,当前系统活跃的事务id列表
  • min_trx_id: 生成ReadView的时候,当前系统活跃最小事务id
  • max_trx_id: 生成ReadView的时候,系统应该分配给下一个事务的id
  • creator_trx_id: 生成ReadView的事务的事务id

在访问某条记录的时候需要按照下面的步骤来判断:

  1. 如果被访问记录的trx_id 和 ReadView中的creator_trx_id 值相同,那么表示当事务在访问它自己修改过的数据,允许访问
  2. 如果被访问记录的trx_id 小于 ReadView中的min_trx_id,说明生成该版本的事务在生成ReadView的时候已经提交,允许访问
  3. 如果被访问记录的trx_id 大于等于 ReadView中的max_trx_id,说明生成该版本的事务在生成ReadView的时候还未提交,不允许访问
  4. 如果被访问记录的trx_id在ReadView中的min_trx_id和max_trx_id之间,就需要判断记录的trx_id是否在m_ids的列表中,如果存在说明该版本的时候在生成ReadView的时候还未提交,不允许访问,如果不在则可以访问。

当出现了不允许访问的时候就沿着版本链找上一条记录,重复以上的判断,直到找到可以访问的数据。

read committed 与 repeatable read最大的区别就是生成 ReadView 的时机不同,read committed是在每次查询的时候都会生成一个ReadView, 而repeatable read只在第一次读取数据的时候生成ReadView。

原文链接: http://herman7z.site