【Java超高频面试题&事务】事务相关高频面试题汇总

简介:

本期文章将汇总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多版本控制,只有没有事务对该版本进行使用才能够清除。


结语

🔥如果文章对你有帮助的话,欢迎💗关注、👍点赞、⭐收藏、✍️评论,支持一下小老弟,蟹蟹大咖们~ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值