mysql事务知识点总结(InnoDB)

本文总结了MySQL InnoDB事务的四大特性:原子性、一致性、隔离性和持久性,并详细解释了事务的隔离级别,包括脏读、幻读、不可重复读的问题。重点探讨了InnoDB如何通过undo log和redo log实现事务的原子性和持久性,以及MVCC在解决并发问题中的作用。同时,提及了Spring事务的7种传播行为。
摘要由CSDN通过智能技术生成

事务的四大特性

原子性一个事务中的操作,要么全都完成,要么全都不完成。如果事务在执行过程中发生了错误,将会被回滚(rollback),就好像从来没有发生过一样。
一致性事务完成后,数据库的完整性没有被破坏。它关注的方面不仅有主键约束、外键约束等等必须要实现的约束,还有更多业务逻辑上的一致性。举个例子,张三转账给李四,张三的账户上少了100,李四的账户上多了100,那么张三少了100而李四多了100这个中间状态就是不可见的。像这种状态也是一致性关注的内容。
持久性即事务对数据库的更改是持久的,不是暂时的,即使系统故障也不会丢失。
隔离性数据库允许多个并发事务对其进行修改的能力。这里有我们熟悉的四大隔离级别,未提交读、已提交读、可重复读和可串行化。

事务的三个问题

即脏读、幻读、不可重复读。
脏读:指在事务A中读到了事务B未提交的数据,也就是所谓的“脏数据”。
不可重复读:指一个事务中两次读取相同的数据,却发生了结果不一致的问题。(此时读到的数据可以是另一个事务已提交过后的数据,因此和脏读不冲突。)
幻读:指一个事务中两次执行相同条件的查找和更新,但第二次会发现莫名其妙地多了一些新的数据行。举个例子,事务A查所有age=15的数据,然后把它们的字段a的值改成111,但与此同时事务B插入了一条age=15的数据,它的a不是111,于是事务A再次查询时发现多了一条数据,且未被更改,这就是幻读问题。
是不是看起来不太明白幻读和不可重复读的区别?没错,都是一个事务中前后两次查询的结果不相同,但不可重复读注重数据的更新和删除,而幻读注重数据的插入。如果使用锁机制来实现这两种隔离级别,在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。所以说幻读和不可重复读最大的区别,就是如何使用锁机制来解决他们产生的问题。

事务的四大隔离级别

它们是未提交读、已提交读、可重复读、可串行化。看它们四个,要结合上面说到的三个问题来看。
未提交读(Read Uncommitted):最低的事务隔离级别,基本上三个问题啥也不能解决。顾名思义,它可以读到其他事务未提交的数据,也就是连最低级别的脏读都无法避免。
已提交读(Read Committed):只能读到其他事务提交过后的数据,可以解决脏读。但不可重复读,原因就如上面所说,可能事务执行过程中数据被其他事务已提交的更改更新了。
可重复读(Repeatable Read):确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。也就是已存在的数据行不会“变化”,但可能无法避免问题,也就是会“多一行新的数据”。
可串行化(Serializable):最高级别的隔离,永远滴神,顾名思义,就是事务在这种隔离级别下执行时就像是串行执行一样。

我们知道,mysql默认的隔离级别是可重复读(RR),那么它就不能解决幻读吗?事实上不是的,InnoDB引擎会用两种机制解决幻读,即MVCC机制和Next-key Lock机制。但它真的完全解决了幻读吗?并没有,确切地说,它解决了读数据下产生的幻读问题,而一旦产生写操作,依然会出现幻读现象。接下来就对此进行详细分析。

事务实现原理

主要总结一下事务的四大特性都是靠什么机制保证的。

原子性

实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的sql语句。而实现以上这句话,依赖的是undo log(回滚日志)。
undo log存储在磁盘上,是逻辑日志,和事务操作是反着来的,也就是说,事务中做了一个insert,undo log里就会多一条delete,事务中update了一条数据,undo log中就多了一条把这条数据update回原样的记录。当一个事务回滚,InnoDB就会根据事务的undo log逐个操作,计算出原来的值,从而达到回滚的效果。
以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态。

持久性

实现持久性的关键,是即使任务执行过程中机器宕机,再重启时也能够顺利复原之前的数据。实现这一要求依赖的是redo log(重做日志)。
redo log是InnoDB存储引擎一个特有的日志,存储在磁盘上,是物理日志。当数据修改时,除了修改Buffer Pool(磁盘缓存,映射了部分磁盘页,类似于一个cache的作用)中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。

隔离性

实现隔离性主要依赖的是锁机制和MVCC机制。所谓MVCC,即多版本并发控制机制,它通过在数据库中保留多个版本的数据解决脏读、幻读、不可重复读等等问题。
首先,我们可以简单地把MVCC理解成每一条数据都带上一个它对应的版本号。举个例子,更新操作时,先获取当前的系统(全局)版本号,再将新数据的版本号标为当前系统版本号,最后将旧数据标志为删除,将删除版本号标记为当前事务。具体的操作可以看这篇文章。需要注意的是,MVCC并不是真的在数据库里存了这么多个版本的数据,而是根据undo log,在需要访问上一个版本的数据时将其计算出来。
MVCC是如何解决脏读、幻读和不可重复读的呢?
脏读:
在这里插入图片描述
T2时间节点上的修改操作会更改zhangsan这条数据的最后修改的版本号。当事务A在T3时间节点读取zhangsan的余额时,会发现数据已被其他事务修改,且状态为未提交。此时事务A读取最新数据后,根据数据的undo log执行回滚操作,得到事务B修改前的数据,从而避免了脏读。

不可重复读:
在这里插入图片描述
当事务A在T2节点第一次读取数据时,会记录该数据的版本号(数据的版本号是以row为单位记录的),假设版本号为1;当事务B提交时,该行记录的版本号增加,假设版本号为2;当事务A在T5再一次读取数据时,发现数据的版本号(2)大于第一次读取时记录的版本号(1),因此会根据undo log执行回滚操作,得到版本号为1时的数据,从而实现了可重复读。

幻读:
在这里插入图片描述
当事务A在T2节点第一次读取0<id<5数据时,标记的不只是id=1的数据,而是将范围(0,5)进行了标记,这样当T5时刻再次读取0<id<5数据时,便可以发现id=2的数据比之前标记的版本号更高,此时再结合undo log执行回滚操作,避免了幻读。(当然这里说的是不加for update关键字的操作,如果加了,那就是正儿八经的Next-Key Lock锁,而不是一个时间戳标记了)
但RR机制并不是可串行化,不能保证完全的隔离,具体例子如下:
在这里插入图片描述
以上部分内容转载自这篇博文,想要深入了解的小伙伴们可以去看原文。

一致性

这个就没有什么具体的实现机制了。确切地说,前面的原子性、持久性和隔离性,最终目的都是为了保证一致性,而它也需要业务逻辑的设计与数据库其他方面的一些限制等等机制进行保障。

spring的7种事务的传播行为

这个纯粹是因为和事务有关,所以在这里copy一个表格记录一下,仅供参考。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值