Mysql的事务隔离级别以及实现原理

977 阅读4分钟

1. 事务的四个隔离级别

Mysql一共有四种事务隔离级别:

隔离级别脏读不可重复读幻读
读未提交
读已提交
可重复读
序列化
  • 脏读:某一个事务,读取了另一个事务未提交的数据。
  • 不可重复读:某一个事务,对同一个数据前后读取的结果不一致。
  • 幻读:一个事务中,相同的范围查询条件,前后读取数据不一致。

“读已提交”和“可重复读”在实现上,数据库里面会创建一个ReadView,访问的时候以ReadView的逻辑结果为准。

① 在“可重复读”隔离级别下,这个ReadView是在事务启动时创建的,整个事务存在期间都用这个ReadView。

② 在“读提交”隔离级别下,这个ReadView是在每个 SQL 语句开始执行的时候创建的。

③ 在“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;

④ 在“串行化”隔离级别下直接用加锁的方式来避免并行访问。

2. read view (快照)

InnoDB在实现MVCC时用到的一致性读视图,用于支持读已提交和可重复读隔离级别的实现。

遗漏的关键控制逻辑 InnoDB RR隔离界别下,MVCC对记录可见性控制,还有如下关键判定逻辑:

1.事务ID并非在事务begin时就分配,而是在事务首次执行非快照读操作(SELECT ... FOR UPDATE/IN SHARE MODE、UPDATE、DELETE)时分配。

注:如果事务中只有快照读,InnoDB对只有快照读事务有特殊优化,这类事务不会拥有事务ID,因为它们不会在系统中留下任何修改(甚至连锁都不会建),所以也没有留下事务ID的机会。虽然使用SELECT TRX_ID FROM INFORMATION_SCHEMA.INNODB_TRX WHERE TRX_MYSQL_THREAD_ID = CONNECTION_ID();查询此类事务ID时,会输出一个很大的事务ID(比如328855902652352),不过这只是MySQL在输出时临时随机分配的一个用于显示的ID而已。

2.每个事务首次执行快照读操作时,会创建一个read_view对象(可以理解为在当前事务中,为数据表建立了一个逻辑快照,read_view对象就是用来控制此逻辑快照的可见范围的)。事务提交后,其创建的read_view对象将被销毁。

read_view对象中有三个关键字段用于判断记录的可见范围。它们分别是trx_ids、low_limit_id、up_limit_id。
1. read_view->trx_ids:创建该read_view时,记录正活跃的其他事务的ID集合。事务ID在集合中降序排列,便于二分查找。
2. read_view->low_limit_id:当前活跃事务中的最大事务ID+1(即系统中最近一个尚未分配出去的事务号)。
3. read_view->up_limit_id:当前活跃事务中的最小事务ID。
  1. 如果记录的版本号比自己事务的read_view->up_limit_id小,则该记录的当前版本一定可见。因为这些版本的内容形成于快照创建之前,且它们的事务也肯定已经commit了。或者如果记录的版本号等于自己事务的事务ID,则该记录的当前版本也一定可见,因为该记录版本就是本事务产生的。

  2. 如果记录的版本号与自己事务的read_view->low_limit_id一样或比它更大,则该版本的记录的当前版本一定不可见。因为这些版本的内容形成于快照创建之后。

不可见有如下两层含义:
1. 如果该记录是新增或修改后形成的新版本记录,则对新增和修改行为不可见,即看不到最新的内容;
2. 如果该记录是标记为已删除形成的新版本记录,则对该删除行为不可见,即可以看到删除前的内容。
  1. 当无法通过4和5快速判断出记录的可见性时,则查找该记录的版本号是否在自己事务的read_view->trx_ids列表中,如果在则该记录的当前版本不可见,否则该记录的当前版本可见。

  2. 当一条记录判断出其当前版本不可见时,通过记录的DB_ROLL_PTR(undo段指针),尝试去当前记录的undo段中提取记录的上一个版本进行4~6中同样的可见性判断,如果可以则该记录的上一个版本可见。