Mysql事务隔离级别是如何实现的?

<事务特性 -> 实现方式>

    1.原子性 ->undo log

事务是一系列操作的集合,这些操作要么全部完成,要么全部不完成。如果在事务执行中发生错误,会被回滚到事务开始前的状态

     2.一致性 ->原子性、持久性、隔离性

事务执行前后,数据库满足完整性约束,数据库保存一致状态

比如A用户给B用户转账,转账前后,他们的余额总和总是一致的

    3.隔离性 ->MVCC / 锁机制

数据库允许多个并发的事务对其进行数据读写与修改,并且这些事务相互独立互不干扰

    4.持久性 ->redo log

事务执行后,事务执行的结果会永久性地保存在磁盘中,即使系统故障也不会丢失

<为什么事务要有隔离级别?或者说,并发事务会引发什么问题?>

     1.脏读:一个事务读到了另一个事务还未提交的事务修改过的数据。还未提交意味着随时可能回滚,那么读到的数据可能就是过期的数据

     2.不可重复读:一个事务内多次读取同一个数据,发现前后两次读出的数据值不一样

     3.幻读:一个事务内多次查询某个条件下的记录数量,发现前后两次读出的记录数量不一样

<用四种隔离级别来规避这几种现象>

  1. 读未提交:一个事务还未提交,它所做的变更就能被其它事务看到
  2. 读提交:一个事务提交了,它所做的变更才能被其它事务看到
  3. 可重复读:一个事务执行过程中看到的数据总是和这个事务启动时看到的数据是一致的(InnoDB存储引擎默认的事务隔离级别)
  4. 串行化:会对记录加上读写锁,当多个事务对一条记录进行读写操作时,发生读写冲突时,事务的执行有先后顺序,需要排队

<不同隔离级别可能发生的现象不同>

Mysql事务隔离级别是如何实现的?_第1张图片

        

要解决脏读问题 -> 升级到读已提交

要解决不可重复读问题 -> 升级到可重复读

要解决幻读问题 -> 不建议升级到串行化!!!(会大大降低事务的并发性)

Tip:虽然可重复读可能会发生幻读,但是它可以做到很大程度上的避免幻读,这也是InooDB存储引擎将它作为默认隔离级别的原因

详细内容:可重复读是否完全解决了幻读问题?

见我的另一篇文章:

Mysql默认的隔离级别“可重复读”是如何解决幻读问题的,完全解决了吗?

<事务隔离级别的具体实现>

  1. 读未提交 -> 直接读取最新的记录
  2. 串行化 -> 添加读写锁来避免事务的并行访问
  3. 读提交、可重复读 -> 通过不同的ReadView(数据快照)创建时机来实现

        具体区别:读提交在每次语句执行前生成一个ReadView,而可重复读在事务启动的时候生成一个ReadView,之后直到事务提交一直沿用这个ReadView

Tip:事务执行开启命令并不意味着就开启了事务

Mysql中有两种开启事务的命令:

1)begin/start transaction ->执行该命令后,只有执行了增删改查操作,事务才会启动

2)start transaction with consistent snapshot  ->执行该命令后,事务就会启动

ReadView的四个字段:

  1. m_ids:创建ReadView时数据库中活跃事务(启动但为提交)的id列表
  2. min_trx_id:m_ids中的最小值
  3. max_trx_id:创建ReadView时当前数据库应该给下一个事务的id值
  4. creator_trx_id :创建ReadView的事务id

聚集索引中的两个隐藏列:

  1. trx_id:修改记录的事务id
  2. roll_pointer:每次修改记录时该旧版本的记录会被写入到undo log日志中,roll_pointer指针会指向该旧版本记录。通过这个指针会形成一个版本链,便于找到旧版本的记录

Mysql事务隔离级别是如何实现的?_第2张图片

那么ReadView的作用在哪呢?

        并发进行的事务会让一条记录产生许多版本,ReadView的作用就是帮助我们判断这条记录的哪个版本是可用的

  1. 如果trx_id == creator_trx_id,说明这个版本的记录是当前事务自己修改得到的,那么它是可以被当前事务访问的
  2. 如果trx_id < min_trx_id,因为min_trx_id是最小的未提交事务的id,trx_id < min_trx_id说明trx_id这个事务已经被提交了,那么已经提交的事务修改的结果,是可用的
  3. 如果trx_id > max_trx_id,由于max_trx_id是下一个对记录进行修改会分配给事务的id,所以trx_id不存在于版本链中,是不可用的
  4. 如果 min_trx_id <= trx_id <= max_trx_id,看看trx_id是否在m_ids中,也就是看它是否是为提交的活跃事务,如果在里面,则该版本不可用,如果不在里面,则说明该事务已经提交了,则该版本可用

<可重复读RR的实现>

某个事务A在启动时创建自己的ReadView,并在事务提交前会一直沿用这个ReadView,ReadView中的m_ids中会有其它活跃事务的id,其它并发进行的事务比如事务B的id要么小于min_trx_id(已经提交),要么在A事务期间提交但是由于A事务的ReadView不会更新,B事务的trx_id始终会在m_ids中,所以不可以读到B事务修改的结果,这样就实现了可重复读的隔离级别

<读提交RC的实现>

某个事务A在读取数据的时候创建了自己的ReadView,并在每次做读取数据操作的时候都会更新这个ReadView,ReadView中的m_ids一开始会有其它并发的活跃事务比如事务B的trx_id,但是当事务B对数据进行修改并提交后,事务A再做读取操作时,创建的ReadView的m_ids中将没有事务B的trx_id在其中,而且,这个trx_id < min_trx_id(当然,也可能trx_id介于min_trx_id和max_trx_id之间,但是因为ReadView更新后,trx_id不在m_ids中了),所以是可以读到的,这样就实现了读提交的隔离级别

通过记录中的两个隐藏列(trx_id和roll_pointer)以及事务的ReadView中的字段的对比,来控制并发事务访问同一个记录的行为,这就是MVCC

你可能感兴趣的:(Mysql,mysql,数据库,java)