MySQL事务基础

42 阅读6分钟

问题的引入

  1. 什么是事务?

事务四大特性:

  • 原子性:undo log(回滚日志)
  • 一致性:
  • 隔离性:隔离级别
  • 持久性:redo log(重做日志)
  1. 先来讨论一下隔离性

因为多个事务并发有可能修改数据导致其他事务读取的数据发生前后不一致的问题:

  • 脏读:脏读是读取到了事务修改的数据但是这个事务还未提交,随时可能发生回滚,导致读取到过期的数据(简单来说就是读取到了未提交的数据)
  • 不可重复读:一个事务执行过程中多次读取数据,两次读取的数据不一致的问题(简单来说就是两次读取到了不一样的数据)
  • 幻读:也是一个事务执行过程中多次查询数据,但是两次查询的结果数量不一致的问题(简单来说就是读取到的数据数量不一致)

其中脏读是最严重的问题,所以隔离性是数据库的重要特性。

  1. 此时引入隔离四种隔离级别来解决这三个问题
  • 读未提交:事务还未提交,作的修改可以被其他事务看见
  • 读提交: 事务提交后作的修改才能被其他事务看见
  • 可重复读: 一个事务执行过程中,它所看到的数据是一致不会改变的
  • 串行化:对事务加上一个锁,使得其他事务只能串行执行,避免并发产生问题。
  1. 四种隔离级别分别解决了什么问题
  • 读未提交:什么都解决不了
  • 读提交:解决脏读问题
  • 可重复读:解决脏读,不可重复读问题(不可重复读是在一个事务里多次读取的数据不一致)
  • 串行化:解决所有问题,但是效率低下
  1. MySQL中默认的是可重复读的隔离级别 但是这个隔离级别不能完全避免幻读的问题

  2. 四种隔离级别实现

  • 读未提交: 不需要实现什么,修改就可以直接读
  • 串行化:通过对读写进行加锁
  • 读提交:通过Read View来实现(创建Read View的时机不同)
  • 可重复读:通过Read View来实现(创建Read View的时机不同)
  1. Read View在MVCC里如何实现?

什么是Read View?

先了解一下两个内容,一个是快照Read View的四个字段,一个是记录里面两个隐藏列

快照Read View

简单来说就是快照,有四个字段

  • m_id: 当前数据库中活跃事务的列表,活跃事务是指启动了但还未提交的事务
  • min_trx_id: 活跃事务列表中最小的id事务
  • max_trx_id: 创建下一个事务时,系统要分配的下一个id
  • creator_trx_id: 创建该Read View的事务id
两个隐藏列

聚簇索引某个记录中的两个隐藏列(InnoDB中)

  • trx_id: 事务对该记录修改时,该事务的id会记录在这
  • roll_pointer: 指向该记录旧版本的指针

什么是MVCC?

通过版本链来控制并发事务访问同一个记录的行为,MVCC就叫多版本并发控制

  1. 可重复读如何工作?

启动事务时生成一个Read View,整个事务期间都使用这个Read View

  1. 第一个事务A,第二个事务B
  2. 先创建事务A,紧接创建事务B
  3. 事务A创建第一个快照,记录m_id:[1],min_trx_id: 1,max_trx_id:2,creator_trx_id: 1 事务B创建第二个快照,记录m_id:[1,2],min_trx_id: 1,max_trx_id: 3,creator_trx_id: 2
  4. 事务B读取数据为100(trx_id为0,比min_trx_id小,所以可以读取)
  5. 事务A修改数据为200,但未提交,这个时候数据的trx_id:1,roll_pointer指向旧版本数据100
  6. 事务B读取发现这条数据trx_id为1,在事务B的Read View的min_trx_id和max_trx_id之间,则需要判断trx_id是否在m_id之间,在说明修改该数据的事务还未提交,不能读取修改后的数据,然后会根据roll_pointer来读取可以读取的数据
  7. 事务A提交,由于是可重复读的隔离级别,事务B还是使用事务创建时创建的Read View,所以还是读取不了最新数据,原因和上面一样 因此无论如何在一个事务中读取的数据都是一致的
  1. 读提交如何工作?

读提交是每次读取数据时都会生成一个新的Read View 例子和上面的一样

重点在第二次读取时(事务A已经修改了数据,但还未提交,由于是读提交隔离级别,所以事务B在读取时会创建一个新的快照,但活跃事务列表里面还有事务A和B) 所以读取还是读取到旧的数据,因为修改数据的事务A还未提交 然后是第三次读取,事务A已经提交,事务B会再次创建一个新的快照,此时的m_id里面只剩事务B的2,读取时发现隐藏列里面的trx_id不在m_id里面,说明修改的数据的事务已经提交,可以读取最新数据

  1. 总结

MVCC简单就是记录里面的两个隐藏列,通过版本链来控制并发事务访问同一个记录的行为,通过Read View来实现隔离性。

每次读取数据时会通过trx_id和Read View先和mix_trx_id来比较如果小于,说明修改数据的事务在快照之前可以读取,如果大于且小于max_trx_id,则可以通过m_id[]来判断trx_id是否在其中,来判断是否能读取,如果不在里面就可以读取,在里面说明修改的事务还未提交,不能读取修改后的数据,然后会根据roll_pointer来读取可以读取的数据。

而读提交隔离级别和可重复读隔离级别的区别就是生成Read View的时机不同,读提交是每次读取数据时都会创建一个快照,而可重复读是创建事务时才创建快照,且在该事务中一直使用此快照