简介:
本期文章将汇总java事务相关的高频面试题,给最近需要找工作的朋友们总结一波,帮助大家全面掌握事务的核心知识点,提升竞争力,为你的面试之旅保驾护航!
Spring中的事务是如何实现的?
1.Spring事务底层是基于数据库事务和AOP机制的
2.首先对于使用了@Transactional注解的Bean,Spring会创建一个代理对象作为Bean
3.当调用代理对象的方法时,会先判断该方法上是否加了@Transactional注解
4.如果加了,那么则利用事务管理器创建一个数据库连接
5.并且修改数据库连接的autocommit属性为false,禁止此连接的自动提交,这是实现Spring事务非常重要的一步
6.然后执行当前方法,方法中会执行sql
7.执行完当前方法后,如果没有出现异常就直接提交事务
8.如果出现了异常,并且这个异常是需要回滚的就会回滚事务,否则仍然提交事务
9.Spring事务的隔离级别对应的就是数据库的隔离级别
10.Spring事务的传播机制是Spring事务自己实现的,也是Spring事务中最复杂的
11.spring事务的传播机制是基于数据库连接来做的,一个数据库连接一个事务,如果传播机制配置为需要新开一个事务,那么实际上就是先建立一个数据库连接,在此新数据库连接上执行sql
spring事务失效的场景
1. 未启用spring事务管理功能
2. 方法不是public类型的
3. 数据源未配置事务管理器
4. 自身调用问题,比如:入口方法A没有事务,在方法内部调用该类中的B方法,B方法的事务会失效,因为入口方法没有事务就是在非代理情况下执行,那么B方法也会非代理执行。
5. 异常类型错误
6. 异常被吞了
7. 业务和spring事务代码必须在一个线程中
8. new出来的对象
Spring事务传播行为(机制)(高频)
1. PROPAGATION_REQUIRED --- required
如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
2. PROPAGATION_REQUIRES_NEW --- required_new
新建事务,如果当前存在事务,把当前事务挂起。
3. PROPAGATION_SUPPORTS --- supports
支持当前事务,如果当前没有事务,就以非事务方式执行。
4. PROPAGATION_NOT_SUPPORTED --- not_supported
以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
5. PROPAGATION_MANDATORY --- mandatory
使用当前的事务,如果当前没有事务,就抛出异常。
6. PROPAGATION_NEVER --- never
以非事务方式执行,如果当前存在事务,则抛出异常。
7. PROPAGATION_NESTED --- nested
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
什么是事务
一组对数据库的操作,把这一组看成一个再给你,要么全部成功,要么全部失败。
举个例子,比如A向B转账,A账户的钱少了,B账户的钱就应该对应增加,这就转账成功了,如果A账户的钱少了,由于网络波动等因素转账失败了,B账户的钱没有增加,那么A账户就应该恢复成原先的状态
事务的四大特性
原子性:指的是一个事务应该是一个最小的无法分割的单元,不允许部分成功部分失败,只能同时成功,或者同时失败
持久性:一旦提交事务,那么数据就应该持久化,保证数据不会丢失
隔离性:两个事务修改同一个数据,必须按顺序执行,并且前一个事务如果未完成,那么中间状态对另一个事务不可见
一致性:数据库处理前后结果应与其所抽象的客观世界中真实状况保持一致,要求任何写到数据库的数据都必须满足预先定义的规则,它基于其他三个特性实现的。
InnoDB如何保证原子性和持久性的
通过undo log 保证事务的原子性,redo log保证事务的持久性
undo log是回滚日志,记录的是回滚需要的信息,redo log记录的是新数据的备份
当事务开始时,会先保存一个undo log,再执行修改,并保存一个redo log,最后再提交事务。如果系统崩溃数据保存失败了,可以根据redo log中的内容,从新恢复到最新状态,如果事务需要回滚,就根据undo log 回滚到之前的状态。
事务并发问题有哪些
脏读:事务A读到了事务B修改还未提交的数据
幻读,也叫虚读:事务A两次读取相同条件的数据,两次查询到的数据条数不一致,是由于事务B再这两次查询中插入或删除了数据造成的
不可重复读:事务A两次读取相同条件的数据,结果读取出不同的结果,是由于事务B再这两次查询中修改了数据造成的
第一类丢失更新:也叫回滚丢失,事务A和事务B更新同一条数据,事务B先完成了修改,此时事务A异常终止,回滚后造成事务B的更新也丢失了
第二类丢失更新:也叫覆盖丢失,事务A和事务B更新同一条数据,事务B先完成了修改,事务A再次修改并提交,把事务B提交的数据给覆盖了
事务隔离级别有哪些,分别能解决什么问题
读未提交:事务读不阻塞其他事务的读和写,事务写阻塞其他事务的写但不阻塞读,能解决第一类丢失更新的问题,
读已提交:事务读不会阻塞其他事务读和写,事务写会阻塞其他事务的读和写,能解决第一类丢失更新,脏读的问题
可重复读:事务读会阻塞其他事务的写但不阻塞读,事务写会阻塞其他事务读和写,能解决第一类丢失更新,脏读,不可重复读,第二类丢失更新问题
串行化:使用表级锁,让事务一个一个的按顺序执行,能解决以上所有并发安全问题
说一下事务的执行流程(Undolog+Redolog)
假设有A=1,B=2,两个数据,现在有个事务把A修改为3,B修改为4,那么事务的执行流程:
当事务开始时,会首先记录A=1到undo log,记录A=3到redo log,和记录B=2到undo log,记录B=4到redo log,然后再将redo log写入磁盘,最终事务提交
串行化有什么特点
串行化是4种事务隔离级别中隔离效果最好的,解决了脏读、可重复读、幻读的问题,但是效果最差,它将事务的执行变为顺序执行,与其他三个隔离级别相比,它就相当于单线程,后一个事务的执行必须等待前一个事务结束。
事务隔离其实就是为了解决上面提到的脏读、不可重复读、幻读这几个问题,下面展示了 4 种隔离级别对这三个问题的解决程度
隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(Read uncommitted) | 可能 | 可能 | 可能 |
读提交(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable reads) | 不可能 | 不可能 | 可能 |
串行化(Serializable) | 不可能 | 不可能 | 不可能 |
MySQL 事务隔离其实是依靠锁来实现的,加锁自然会带来性能的损失。而读未提交隔离级别是不加锁的,所以它的性能是最好的,没有加锁、解锁带来的性能开销。但有利就有弊,这基本上就相当于裸奔啊,所以它连脏读的问题都没办法解决。
解释一下事务并发丢失更新问题,如何解决
第一类丢失更新:也叫回滚丢失,事务A和事务B更新同一条数据,事务B先完成了修改,此时事务A异常终止,回滚后造成事务B的更新也丢失了
第二类丢失更新:也叫覆盖丢失,事务A和事务B更新同一条数据,事务B先完成了修改,事务A再次修改并提交,把事务B提交的数据给覆盖了
SQL标准中的四种隔离级别,读未提交,读已提交,可重复读,串行化,都能解决第一类数据更新丢失问题
对于第二类丢失更新问题,可以使用悲观锁也就是串行化来解决,也可以使用乐观锁的方式,比如加一个版本号管理来解决
InnoDB事务隔离的实现原理是什么
隔离的实现主要利用了读写锁和MVCC机制
读写锁,要求在每次读操作时需要获取一个共享锁,写操作时需要获取一个写锁。共享锁之间不会产生互斥,共享锁和写锁,写锁与写锁之间会产生互斥。当产生锁竞争时,需要等一个操作的锁释放,另一个操作才能获得锁
MVCC,多版本并发控制,它是在读取数据时通过一种类似快照的方式将数据保存下来,不同的事务看到的快照版本是不一样的,即使其他事务修改了数据,但是对本事务仍然是不可见的,它只会看到第一次查询到的数据
可重复读是只在事务开始的时候生成一个当前事务全局性的快照,而读提交则是每次执行语句的时候都重新生成一次快照
事务是怎么实现持久性的?
与事务实现原子性用到的undo_log文件相似,事务实现持久性用到了一个叫做redo_log的文件。redo_log记录的是新数据的备份。在事务提交之前,会先保存redo_log当系统崩溃了之后,可以根据redo_log还原数据。数据会以异步的方式刷盘的Mysql磁盘文件。
redo_log文件:
体积小,只记录那一夜修改了什么数据,刷盘快。
数据库文件是随机io的,但是redo_log是在末尾追加,是顺序IO,性能更好。
redo log它由两部分组成,内存中的 redo log buffer,磁盘上的 redo log file
1.redo log file 由一组文件组成,当写满了会循环覆盖较旧的日志,这意味着不能无限依赖 redo log,更早的数据恢复需要 binlog
2.buffer 和 file 两部分组成意味着,写入了文件才真正安全,同步策略由参数innodb_flush_log_at_trx_commit 控制
0 - 每隔 1s 将日志 write and flush 到磁盘
1- 每次事务提交将日志 write and flush(默认值)
2 - 每次事务提交将日志 write,每隔 1s flush 到磁盘,意味着 write 意味着写入操作系统缓存,如果 MySQL 挂了,而操作系统没挂,那么数据不会丢失
InnoDB 有一个后台线程,每隔 1 秒,就会把 redo log buffer 中的日志,调用 操作系统函数 write 写到文件系统的page cache,然后调用操作系统函数 fsync 持久化到磁盘文件。
参数值为0,redo log不进磁盘表示不刷写Redo Log到磁盘,即异步写入策略。事务提交时,Redo Log的修改操作只会写入到操作系统的页缓存中,并不会马上刷写到磁盘。这样可以提供最好的写入性能,但在数据库崩溃或发生故障时,可能会造成一定程度的数据丢失。
参数值为1,redo log进磁盘【默认值】表示同步刷写Redo Log到磁盘。事务提交时,Redo Log的修改操作会立即写入磁盘并等待IO操作完成。确保数据持久性的同时,也会对性能产生一定的影响。这是最常用的设置,适合大多数应用场景。
参数值为2,redo log进os cache缓存。表示每次事务提交时将Redo Log的修改操作写入磁盘,但不会等待IO操作完成。事务提交时,Redo Log会先写入到操作系统的页缓存(page cache),然后由后台线程异步地将数据刷写到磁盘。这种设置可以提供较好的性能和一定程度的数据保护,但仍然存在一定的风险。
刷盘策略选择
选择适当的innodb_flush_log_at_trx_commit值取决于对数据的持久性和性能的需求。如果对数据的持久性要求非常高,可以将其设置为1。如果对性能要求较高且可以接受一定程度的数据丢失,可以将其设置为0。如果在保证一定程度的数据保护的同时追求更好的性能,可以选择设置为2。
可以通过修改MySQL配置文件中的参数设置来调innodb_flush_log_at_trx_commit值,并重启MySQL服务使其生效。
我们通常建议是设置为1。也就是说,提交事务的时候,redo日志必须是刷入磁盘文件里的。这样可以严格的保证提交事务之后,数据是绝对不会丢失的,因为有redo日志在磁盘文件里可以恢复你做的所有修改。
事务是怎么实现原子性的?
Mysql的事务的原子性是通过Innodb的undo_log来实现的。undo_log记录了回滚需要的信息。在进行事务操作时,如果失败就可以通过undo_log文件修改会之前的数据状态:
如果是update,就会根据undo_log文件修改回之前的数据。
如果是insert,就会记录主键到undo_log文件,根据主键删除数据。
如果是delete,就会记录主键到undo_log文件,insert回之前的数据。
深入undo_log:InnoDB对undo log文件的管理采用段的方式,也就是回滚(rollback segment) 。每个回滚段记录了 1024 个 undo log segment ,每个事务只会使用一个undo log segment。
在MySQL5.5的时候,只有一个回滚段,那么最大同时支持的事务数量为1024个。在MySQL 5.6开始,InnoDB支持最大128个回滚段,故其支持同时在线的事务限制提高到了 128*1024 。
undo_log存储的位置是在mysql目录下的ibdata1文件中,当然也可以设置undo_log的存储位置。新增类型的,在事务提交后就可以提交了。但是修改类型的还不行,还需要进行mvcc多版本控制,只有没有事务对该版本进行使用才能够清除。
结语
🔥如果文章对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下小老弟,蟹蟹大咖们~