面试题一:什么是事务?为什么需要事务?
事务(Transaction)是保证数据库可靠性和稳定性的一种机制,单个逻辑工作单元执行的一系列操作,这些操作要么全部完成,要么全部不完成,是数据库环境中的最小工作单元。事务具有四个特征,也被称为(ACID):
- 原子性:事务中包含的所有操作要么完成,要么不完成。假设你要从你的储蓄账户向你的支票账户转账100元。这个操作实际上包含两个步骤:从储蓄账户扣除100元,然后在支票账户中增加100元。这两个步骤必须都完成,或者都不完成。如果在转账过程中发生错误(例如,扣款成功但是存款失败),那么整个事务应该被回滚,就好像从未发生过一样
- 一致性:事务执行的结果必须是数据库从一个一致性状态到另一个一致性状态。在转账事务开始和结束时,你的总资产(储蓄账户和支票账户的总和)应该保持不变。如果你开始时有1000元,那么无论转账多少次,你结束时总资产(储蓄账户和支票账户的总和)应该有1000元。
- 隔离性:一个事务的执行不能被其他事务所干扰。指的是正在执行的转账业务没有提交之前,其他事务能不能看到转账的详情,不同的隔离级别看到的数据是不一样的,例如最低的隔离级别读未提交,它是能读取转账执行一半未提交的数据的(但这个数据位脏数据,可能提交也可能回滚)
- 持久性:一旦事务提交,它对数据库中的改变是永久性的。即使在事务完成后立即发生系统故障,你的支票账户中也应该有那100元
为什么要使用事务?
事务提供了一种逻辑上的一致性和数据完整性的机制,以确保对数据库的更改是可靠性和可恢复性。例如,在银行转账的场景中,如果在转账过程中的任何一步出现问题,如网络异常或服务器宕机等,没有事务的情况下,我们需要自己处理各种异常情况以保证双方账户金额的准确性,这会使编程的复杂性急剧上升。而有了数据库层面的事务保证,能够降低我们的开发难度,许多问题可以交给事务去处理。因此,事务的存在可以简化编程模型,使得在访问和更新数据库时更加安全和可靠
面试题二:事务有哪些特性?举例说明一下
事务具有四个特征,也被称为(ACID):
- 原子性:事务中包含的所有操作要么完成,要么不完成。假设你要从你的储蓄账户向你的支票账户转账100元。这个操作实际上包含两个步骤:从储蓄账户扣除100元,然后在支票账户中增加100元。这两个步骤必须都完成,或者都不完成。如果在转账过程中发生错误(例如,扣款成功但是存款失败),那么整个事务应该被回滚,就好像从未发生过一样
- 一致性:事务执行的结果必须是数据库从一个一致性状态到另一个一致性状态。在转账事务开始和结束时,你的总资产(储蓄账户和支票账户的总和)应该保持不变。如果你开始时有1000元,那么无论转账多少次,你结束时总资产(储蓄账户和支票账户的总和)应该有1000元。
- 隔离性:一个事务的执行不能被其他事务所干扰。假设你正在从储蓄账户向支票账户转账,同时你的朋友也正在查看你的总资产。在转账事务完成之前,你的朋友不应该看到任何中间状态
- 持久性:一旦事务提交,它对数据库中的改变是永久性的。即使在事务完成后立即发生系统故障,你的支票账户中也应该有那100元
面试题三:MySQL如何保证事务四大特性?
MySQL通过以下几种方式保证事物的四大特性:
- 原子性是通过Undo Log(回滚日志)来保证的。InnoDB使用日志(Undo Log)来记录事务的操作,包括事务开始、修改数据和事务提交等。如果十五执行失败或者回滚,InnoDB可以使用日志来撤销已经执行的操作,确保 事物的原子性
- 持久性是通过Redo Log(重做日志)来保证的。在事务提交之前,InnoDB会将事物的修改操作先写入事务日志(Redo Log),然后再将数据写入磁盘。即使在系统崩溃或者断电的情况下,InnoDBb还是可以通过重放事务日志来恢复数据,确保事务的持久性
- 隔离性是通过MVCC(多版本并发控制)和锁机制来保证的
- 一致性是通过各种约束,如主键、外键、唯一约束等,加上事务的持久性、原子性和隔离性来保证的
面试题四:在日常开发中哪些功能会使用到事务?举例说明一下
在日常开发中只要舍恩基达奥多张表要一起执行的场景,要么一起成功,要么一起失败,都会使用到事务。例如以下场景:
- 银行转账业务:需要给一账户增加钱,一个账号减少钱
- 电商下单业务:在电商系统中的下单业务也需要事务,需要将账户余额扣减、库存扣减、添加订单等操作,这些都需要放到一个事务里
- 用户中心完善资料业务:当涉及一个系统中有用户完善资料的场景中通常也需要使用事务,因为这个操作通常至少要修改两个表,一个是修改用户主表信息,然后再给用户积分表添加完善资料的积分操作,所以这种场景需要使用到事务
面试题五:在开发中怎样使用事务的?
在开发中使用事务主要涉及以下几个方面:
- 事务的基础知识:事务的四大特性是原子性、一致性、隔离性和持久性。事务的隔离级别包括读未提交、读已提交、可重复读和串行化。
- 事务的传播行为:事务传播行为指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。例如,如果方法A(事务方法)调用了方法B(事务方法),那么方法B是继续在调用者方法A的事务中运行,还是为自己开启一个新的事务运行,这就是由方法B的事务传播行为决定的。
- 使用@Transactional注解:在方法上加入@Transactional注解后,这个方法就成为了事务方法。如果方法中出现了RuntimeException(运行时异常),事务会进行回滚,如果出现了checkedException (编译时异常),则不会回滚。我们可以在@Transactional注解中进行手动配置,以实现方法抛出具体异常进行回滚的逻辑。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(User user) {
try {
userRepository.save(user);
} catch (Exception e) {
// 如果在保存用户时出现异常,事务将回滚
throw new RuntimeException("Unable to save user", e);
}
}
}
- 异常处理:如果我们在事务方法中,手动捕获了异常,并没有让事务抛出去,也没有手动指定需要回滚,那么事务方法即使出现异常,也会提交事务。
- 事务方法调用事务方法:如果事务方法A调用了事务方法B,并且我们想让B出现异常后,A捕获异常不影响主流程,我们可以把B方法的传播机制设置为REQUIRED_NEW,这样A、B方法就是两个不同的事务,互不影响。
面试题六:MySQL中有哪些事务隔离级别?
MySQL中事务隔离级别主要是分为以下四种:
读未提交(Read Uncommitted):最低的隔离级别,事务中未提交的修改数据,可以被其他事务读取到
优点:并发性能最好,读到的数据最新
缺点:存在脏读(Dirty Read)问题,即读取到未提交的数据,可能导致数据不一致
读已提交(Read Commited):事务中未提交的修改数据,不会被其他事务读取到,此隔离级别看到的数据,都是其他事务已经提交的数据
优点:避免脏读问题
缺点:存在不可重复读(Non-Repeatable Read)问题,即同一个事务中,不同时间读取的数据可能不同
可重复读(Repeatable Read):是指在一个事务中,多次执行相同的查询语句可能会得到不同的结果,因为其他并发事务在该事务正在进行时修改了数据
优点:解决了不可重复读问题
缺点:存在幻读(Phanton Read)问题,即在一个事务中,两次查询同一范围的记录,但第二次查询却发现了新的纪录
串行化(Serializable):最高的隔离级别,将所有的事务串行化执行(一个执行完成,执行下一个),保证了数据的完全隔离
优点:避免了幻读问题
缺点:并发性能最差,可能导致大量锁等待和死锁
MySQL中事务隔离级别就是为了解决咋能都、不可重复读和幻读问题等,这四种隔离级别与三个问题之间的关系如下:
面试题七:不可重复读和幻读有什么区别?
不可重复读:是指在同一事务中,不同时间读取的数据可能不一样。因为其他并发事务在该事务正在进行时修改了数据
幻读:是指在一个事务中,多次执行相同的查询语句可能会返回不同的结果集,因为其他并发事务在该事务正在进行时插入了新的数据行。
不可重复读和幻读都是并发事务引起的读一致性问题,但是两者关注的侧重点和解决方案不同:
侧重点不同:
不可重复读关注的是一行数据的变化,它是指在同一事务中,多次读取同一行数据的结果不一样。这是由于其他并发事务对同一行数据进行了修改(例如更新操作),导致两次读取之间数据发生变化
幻读关注的是范围数据的变化,它指的是在同一事务中,多次查询同一个范围的数据时,结果集的行数发生变化。这是由于其他并发事务在查询范围内插入了新的数据或者从中删除了数据,导致两次查询之间结果集中的行数发生变化
解决方法不同:
不可重复读通常是使用行锁来解决,因为他关注的是一行数据
幻读通常使用间隙锁来解决,因为他关注的是范围数据
面试题八:如何解决不可重复读和幻读的问题?
不可重复读和幻读都可以通过设置事务的隔离级别来解决,将事务隔离级别设置成串行化,那么不可重复读和幻读就可以解决了。但是串行化的执行效率太低了,所以日常工作中我们使用以下几种方式解决:
- 解决不可重复读问题:将数据库的隔离级别设置为可重复读(RR)级别(它是MySQL默认事务隔离级别)
- 解决幻读问题:通过MVCC+MySQL中的锁机制来解决
面试题九:什么是MVCC机制?它能解决幻读问题吗?为什么?
MVCC是多版本并发控制。它是一种并发控制方法,一般在数据库管理系统中,实现对数据库的并发访问。MVCC的作用其实就是避免同一个数据在不同事务之间竞争,提高系统的并发性能。它的特点如下:
- 允许多版本同时存在,并发执行
- 不依赖锁机制,性能高
- 只在读已提交和可重复读的事务隔离级别下工作
使用MVCC机制解决了RR隔离级别中,部分幻读问题,但是有完全把全部幻读问题都解决:
- MVCC解决了RR隔离级别中,快照读的幻读问题。多次查询快照读的时候,因为RR级别是复用Read View(读视图),所以没有幻读问题
- MVCC解决不了RR隔离级别中,如果遇见快照读和当前读(读取当前最新数据)中间发生了添加操作,那么么Read View不能复用,就会出现幻读问题
快照读: 是指在一个事务中,读取的数据版本是在事务开始时已经存在的数据版本,而不是最新的数据版本。这种读取方式提供了事务在执行期间看到的数据视图的一致性,select 查询就是快照读
当前读: 是指在事务中读取最新的数据版本,以下几种操作都是当前读
- select ... for update;
- select ... lock in share mode;
- insert ...
- update ...
- delete ...
所以说 MVCC 可以解决 RR 级别中快照读的幻读问题,但解决不了 RR 级别中的当前读的幻读问题,因为当前读是读取最新数据,此时 MVCC 机制也解决不了幻读问题了,但可以使用锁 (Next-Lock) 配合 MVCC 底解决幻读问题
面试题十:MySQL 有哪些重要的日志?
MySQL中重要的日志有以下几种:
- 慢查询日志(Slow Query Log): 记录执行时间超过指定值的查询语句。慢查询日志可以帮助识别性能较差的查询语句,以便进行优化。此日志默认关闭,需要手动开启
- 二进制日志(Binary Log): 记录对数据库进行更改的所有操作,包括INSERT、UPDATE、DELETE 等。二进制日志可以用于数据恢复、主从复制和数据同步等场景。
- 回滚日志(Undo Log): InnoDB 引擎中的日志,主要用于事务回滚和 MVCC 机制。重做日志(Redo Log): InnoDB 引擎中的日志,主要用于掉电或其他故障恢复的持久化日志
- 重做日志(RedoLog):InnoDB引擎中的日志,主要用于掉电或其他故障恢复的持久化日志