MySQL实现事务隔离原理

本文主要就自己的理解讲述MySQL是如何实现事务的隔离的

事务

众所周知,事务即一组行为,这一组行为处理了业务上的一个完整的逻辑链路。比如转账,检查A的余额,扣钱,更新B的余额,这三步要么一起成功要么都失败(回滚)。

事务具有四大特性,ACID,原子性,一致性,隔离性,持久性。
1.原子性用回滚来保证,通过undo log记录了一个隐藏字段DATA_ROLL_PTR指向undo log中旧版本的数据来进行数据回滚,如果事务失败则rollback,这里不过多解释
2.一致性其实是通过另外三个特性来满足的。
3.隔离性通过设置四个隔离级别按照业务场景选择自己需要的隔离级别,下面会详细解释。
4.持久性通过redo log,在commit前先写入redo log,确保commit成功,这时候就算断电也能在重启后完成数据的持久化。

事务的隔离级别

事务为啥要隔离?并发环境下操作数据库,不同的会话如果同时读还好,同时写,或者同时读写呢?隔离确保事务之间不会相互影响,让业务逻辑正确的走下去。并发环境会出现以下4种单线程不会出现的问题:
1.sessionA还没commit,别的session去读到未commit的数据,这时候A回滚,那其他的会话读取到的数据岂不是有问题?这就叫脏读。比如:财务给张三发了1000元的工资,然后张三查询自己的账户,果然多了1000元,结果财务操作过程有误,事务回滚。当张三再查账户时,却发现账户没钱了以为被盗了,这就有大问题
2.那么我们只读提交了事务的数据就行了,但是如果我比较慢,我先读取数据x的值,然后进行处理,处理期间,别的session修改了x的值并且commit了,那么我在一次事务期间前后读取的值就是不一样的,这就叫不可重复读。比如:张三去买新手机,要1000元,扣款前看了一眼账户上有1000元,扣钱期间,他老婆花了500买了新衣服,这时候扣钱就失败。
3.确保一次事务期间读取的数据一致后,先读取x=5的数据有一条,这时候别的事务提交一条insert插入了一条新的x=5的数据,这种条件范围数量发生变化被称为幻读比如:老板对张三这个名字不爽,决定扣公司里所有叫张三的人的工资1000元,人事根据名字拿到工资数据一起扣钱,这期间又入职了个叫张三的,扣完后发现怎么还有个张三没扣钱,跟幻觉一样。
4.更新丢失就是两个事务在并发下同时进行更新,后一个事务的更新覆盖了前一个事务更新的情况。由于目前互联网项目大多是分布式项目,正常来说会先通过redis加上分布式锁防止同时操作,到了数据层MySQL是通过行锁,间隙锁,next-key lock来处理并发写。

下面来说数据库提供的隔离级别,并发程度由高到低,安全性由低到高:

  1. 读未提交(READ UNCOMMITTED),字面意思,啥也没限制,几种情况都可能出现

  2. 读提交 (READ COMMITTED),限制事务不会读取到还没提交的事务修改的数据,解决脏读

  3. 可重复读 (REPEATABLE READ),其他事务修改了数据,不会影响一开始读取的数据,解决脏读、不可重复读

  4. 串行化 (SERIALIZABLE),串行,事务排着队一个个来,解决脏读、不可重复读、幻读

详解

MySQL默认的隔离级别是可重复读,而目前其他如Oracle等数据库默认采用读提交为默认隔离级别。是因为MySQL在5.0之前的版本中有bug,是由binlog导致的主从不一致问题。这里后面再讲。下面来讲讲隔离级别实现的原理:

读未提交:

性能最好,并发性最高,安全性最低,其实就是没有做隔离,人人都能直接改数据。

串行化:

性能最差,并发性最低,安全性最高,其实就是读的时候加共享锁(读锁),也就是其他事务可以并发读,但是不能写。写的时候加排它锁(写锁),其他事务不能并发写也不能并发读。

读提交与可重复读:

这俩可以放到一起说。MVCC(多版本并发控制)是MySQL的一个很重要的概念,实现的方式是快照和undo log,每行数据会有三个隐藏字段,分别是DB_ROW_ID(如果没有指定主键列会帮你加上这个作为主键),DB_TRX_ID使其产生的事务的 id,事务 ID 记为 transaction id,它在事务开始的时候向事务系统申请,按时间先后顺序递增。DB_ROLL_PTR向当前记录项的undo log信息。


 

按照上面这张图理解,一行记录现在有 3 个版本,每一个版本都记录这使其产生的事务 ID,比如事务A的transaction id 是100,那么版本1的row trx_id 就是 100,同理版本2和版本3。

在上面介绍读提交和可重复读的时候都提到了一个词,叫做快照,学名叫做一致性视图,这也是可重复读和不可重复读的关键,可重复读是在事务开始的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照。

对于一个快照来说,它能够读到那些版本数据,要遵循以下规则:

  1. 当前事务内的更新,可以读到;
  2. 版本未提交,不能读到;
  3. 版本已提交,但是却在快照创建后提交的,不能读到;
  4. 版本已提交,且是在快照创建前提交的,可以读到;

利用上面的规则,再返回去套用到读提交和可重复读的那两张图上就很清晰了。还是要强调,两者主要的区别就是在快照的创建上,可重复读仅在事务开始时创建一次,而读提交每次执行语句的时候都要重新创建一次。

MySQL在可重复读这一隔离级别已经解决了幻读的问题:

MySQL通过next-key lock,锁住当前行以及它的左右的两个区间(行锁和间隙锁的组合),来防止对某一条件的数据进行修改时插入或删除了符合条件的数据,解决了幻读的问题。

比如事务A更新所有的id=10的数据的时候,由于条件 where age = 10 ,数据库不仅在 age =10 的行上添加了行锁,而且在这条记录的两边,也就是(负无穷,10]、(10,30]这两个区间加了间隙锁,这个区间是根据索引这颗b+tree来构建的,从而导致其他插入操作无法完成,只能等待事务A提交。不仅插入 age = 10 的记录需要等待事务A提交,age<10、10<age<30 的记录页无法完成,而大于等于30的记录则不受影响,这足以解决幻读问题了。

这是有索引的情况,如果 age 不是索引列,那么数据库会为整个表加上间隙锁。所以,如果是没有索引的话,不管 age 是否大于等于30,都要等待事务A提交才可以成功插入。

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MySQL事务隔离级别原理是基于并发控制来实现的。在MySQL中,有四个事务隔离级别:读未提交(Read Uncommitted)、读提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。每个隔离级别都有不同的数据可见性和并发控制机制。 在MySQL中,默认的隔离级别是可重复读(Repeatable Read),它通过使用多版本并发控制(MVCC)来实现。MVCC使用了两个关键的数据结构:Undo Log和Read View。 Undo Log是用于实现事务的回滚和并发控制的机制。当一个事务对数据进行修改时,会将旧版本的数据保存在Undo Log中,以便在事务回滚或其他事务需要访问该数据时使用。 Read View是每个事务的一个快照,用于确定事务可见的数据范围。每个事务开始时,会创建一个Read View,并根据事务隔离级别确定可见的数据范围。在可重复读隔离级别下,Read View会在事务开始时记录当前数据库中的所有活跃事务ID,并将这些事务对应的Undo Log应用到Read View中,从而确定事务能够看到的数据范围。 在并发执行的过程中,MySQL会根据不同的隔离级别和事务的读写操作进行数据的读取和写入。对于读操作,会根据事务隔离级别和Read View进行判断,确定读取的数据是否可见。对于写操作,会使用锁机制和Undo Log来保证事务的一致性和隔离性。 需要注意的是,MySQL的不同存储引擎对事务隔离级别的支持也有所不同。例如,MyISAM引擎不支持事务,而InnoDB引擎则支持事务,并提供了更强的并发控制机制。 总结起来,MySQL事务隔离级别原理是通过使用多版本并发控制(MVCC)和锁机制来实现的。每个事务在开始时会创建一个Read View来确定可见的数据范围,同时使用Undo Log来支持事务的回滚和并发控制。不同的隔离级别会决定事务能够看到的数据范围和并发控制的方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值