中小厂数据库事务高频面试题

数据库事务面试题

1. 什么是数据库事务?

一句话:事务是一组要么全成功、要么全失败的操作。

展开:比如转账------A扣钱和B加钱必须同时完成,不能只扣不加。

加分点:事务保证了数据不会停留在中间状态。


2. 事务的 ACID 四大特性是什么?

A(原子性) :操作不可拆分,全做或全不做。
C(一致性) :事务前后数据约束不变(如总金额守恒)。
I(隔离性) :多个事务互不干扰。
D(持久性):提交后数据永久保存,宕机也不丢。

加分点:一致性是最终目的,AID 是为 C 服务的。


3. 什么是脏读?

一句话 :读到了别的事务还没提交的数据。

例子

事务A把余额从100改成200(未提交),事务B读到200。

如果A回滚,B读到的200就是脏数据。

加分点 :脏读是隔离级别最低时才出现的问题。


4. 什么是不可重复读?

一句话 :同一事务内,同一行数据前后读的结果不一样。

例子

事务A第一次查余额100,事务B改成200并提交,事务A再查变成200。

加分点 :侧重修改(UPDATE) ,幻读侧重新增/删除


5. 什么是幻读?

一句话 :同一事务内,两次查询返回的记录条数不一样。

例子

事务A查用户列表共10条,事务B插入一条新用户并提交,事务A再查变成11条。

加分点 :MySQL的RR级别通过间隙锁基本解决了幻读,但严格来说只在串行化下完全避免。


6. 数据库有哪几种隔离级别?

级别 脏读 不可重复读 幻读
读未提交
读已提交
可重复读 MySQL基本避免
串行化

加分点:隔离级别越强,并发性能越差。


7. MySQL 默认隔离级别是什么?解决了哪些问题?

一句话:默认可重复读(RR)。

解决了 :脏读、不可重复读。
幻读 :通过 MVCC + 间隙锁,大部分场景避免,但严格完全避免需要串行化。

加分点:面试官问"RR怎么解决幻读"时,可以说"快照读用MVCC,当前读用间隙锁"。


8. 什么是 MVCC?

一句话:多版本并发控制,让读不阻塞写,写不阻塞读。

原理:每条数据有隐藏的版本字段 + undo log 记录历史版本。

好处 :在 RR 和 RC 级别下,读操作读的是事务开始时的快照,不用加锁。

加分点:MVCC 是 MySQL InnoDB 高并发的核心机制。


9. Spring 事务管理有哪两种方式?

方式 说明 适用场景
编程式 手写 begin/commit/rollback 精细控制、复杂逻辑
声明式 @Transactional 简单易用,项目主流

加分点:实际开发几乎都用声明式,编程式很少用。


10. Spring 事务的 7 种传播机制,常用哪几个?

常用3个

机制 行为
REQUIRED 有事务就用,没有就新建(默认)
REQUIRES_NEW 挂起当前事务,新建一个独立事务
NESTED 嵌套事务,外层回滚内层也回滚,内层异常外层可选回滚

加分点:REQUIRES_NEW 和 NESTED 区别常考------前者独立提交/回滚,后者依赖于外层。


11. @Transactional 注解常用属性有哪些?

属性 作用
propagation 传播机制
isolation 隔离级别
rollbackFor 指定哪些异常回滚
noRollbackFor 指定哪些异常不回滚
timeout 超时时间(秒)
readOnly 是否只读事务(优化性能)

加分点:readOnly = true 可以走只读优化,适合查询场景。


12. @Transactional 默认对哪些异常回滚?

一句话 :只对 RuntimeException 和 Error 回滚。

受检异常 (如 IOException、SQLException)默认不回滚

解决方案 :手动指定 @Transactional(rollbackFor = Exception.class)

加分点 :这是一个高频踩坑点,面试时主动说出来很加分。


13. @Transactional 失效场景有哪些?(高频)

场景 原因
方法不是 public Spring 代理只能拦截 public
类没被 Spring 管理 没加 @Service 等
自身调用(this.方法) 没走代理
异常被 try-catch 吞掉 没抛出异常
数据库引擎不支持事务 如 MyISAM
传播机制设置错误 如改成 NOT_SUPPORTED

加分点:可以说"我在项目里遇到过自身调用失效,后来通过注入自身或放到另一个Service解决"。


14. 编程式事务怎么用?

两种方式

Java 复制代码
// 方式1:TransactionTemplate
transactionTemplate.execute(status -> {
    // 业务代码
    return result;
});

// 方式2:PlatformTransactionManager
TransactionStatus status = transactionManager.getTransaction(def);
try {
    // 业务代码
    transactionManager.commit(status);
} catch (Exception e) {
    transactionManager.rollback(status);
}

加分点 :适合一个方法中有多个事务边界复杂条件回滚的场景。


15. 分布式事务了解吗?简单说下

一句话:跨多个数据库/服务的事务,不能靠本地事务解决。

常见方案

方案 特点
2PC 强一致,但性能差、有阻塞风险
TCC 业务侵入强,性能较好
可靠消息最终一致性 实用主流,如 RocketMQ 事务消息
Seata 框架简化开发,AT模式无侵入

加分点:小厂问到这个,能说出"最终一致性 + 补偿机制"就足够,不用太深。

场景题

场景题1:下单扣库存,事务怎么加?

题目

用户下单:需要「扣库存 + 创建订单 + 扣余额」,三个操作。请问 @Transactional 应该加在哪里?有什么注意事项?

追问

  • 如果库存够、余额够,但创建订单失败了,库存和余额会回滚吗?
  • 如果库存扣成功,余额扣失败了,怎么保证一致性?

标准答案

1. 事务加在Service层方法上:

java 复制代码
@Service
public class OrderService {
    
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(OrderDTO dto) {
        // 1. 扣库存
        stockService.deductStock(dto.getProductId(), dto.getQuantity());
        
        // 2. 扣余额
        accountService.deductBalance(dto.getUserId(), dto.getAmount());
        
        // 3. 创建订单
        orderMapper.insert(order);
    }
}

2. 回答追问:

  • 只要三个操作在同一个事务方法里,任何一个失败,全部回滚
  • 余额扣失败 → 库存回滚、订单不回创建 → 保证最终一致

3. 注意事项(加分点):

  • 必须指定 rollbackFor = Exception.class,防止受检异常不回滚
  • 扣库存和扣余额操作要先扣后创建订单(防止超卖)
  • 高并发场景:扣库存要用乐观锁Redis预扣 ,不能用简单update stock set num = num - 1(会超卖)

面试官想听:

  • 知道事务加在Service层
  • 知道要指定rollbackFor
  • 能说出高并发下事务的局限性(锁时间长、需要结合其他方案)

场景题2:批量导入Excel,一条失败全部回滚吗?

题目

需要批量导入1万条用户数据,要求:要么全部成功,要么全部失败。@Transactional 直接加在循环方法上可以吗?有什么问题?

追问

  • 如果改成「一条失败只跳过继续执行下一条」,怎么做?
  • 怎么避免大事务问题?

标准答案

1. 直接加@Transactional的问题:

java

java 复制代码
// ❌ 错误写法
@Transactional
public void importUsers(List<User> list) {
    for (User user : list) {
        userMapper.insert(user); // 1万次插入都在一个事务里
    }
}

问题:

  • 事务持续时间太长(可能几十秒甚至几分钟)
  • 数据库连接一直被占用
  • 锁持有时间长,容易死锁
  • 回滚日志巨大,回滚慢

2. 全部成功或全部失败的方案:

  • 可以先校验所有数据(格式、重复等)
  • 校验通过后再开启事务批量插入(用batch insert
java 复制代码
@Transactional(rollbackFor = Exception.class)
public void importUsers(List<User> list) {
    // 前置校验全部通过后
    userMapper.batchInsert(list); // 一次插入,不是循环
}

3. 一条失败只跳过继续执行的方案:

  • 不能在一个事务里
  • 编程式事务 + 记录失败日志
java 复制代码
public void importUsersWithSkip(List<User> list) {
    List<String> errorMessages = new ArrayList<>();
    for (User user : list) {
        try {
            userService.insertWithNewTransaction(user);
        } catch (Exception e) {
            errorMessages.add(user.getId() + "失败:" + e.getMessage());
        }
    }
    // 最后返回或保存错误日志
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertWithNewTransaction(User user) {
    userMapper.insert(user);
}

面试官想听:

  • 知道大事务的危害
  • 知道两种需求的不同实现方式
  • 知道REQUIRES_NEW可以拆成小事务

场景题3:方法内部调用 @Transactional 为什么失效?怎么解决?

题目

下面代码,调用 orderService.create() 时,事务生效吗?调用 orderService.save() 时呢?

java 复制代码
@Service
public class OrderService {
    
    public void create(Order order) {
        this.save(order);  // 内部调用
    }
    
    @Transactional
    public void save(Order order) {
        orderMapper.insert(order);
        // 可能抛异常
    }
}

追问

  • 为什么this.save()事务会失效?
  • 给出3种解决方案

标准答案

1. 结论:

  • 调用 orderService.create() → 事务不生效
  • 直接调用 orderService.save() → 事务生效

2. 原因:

  • Spring事务通过代理实现
  • this.save() 走的是真实对象,不是代理对象,所以事务切面不执行
  • 只有通过代理对象调用方法,事务才会生效

3. 三种解决方案:

java 复制代码
// 方案1:注入自己(推荐)
@Service
public class OrderService {
    @Autowired
    private OrderService self; // 注入代理对象
    
    public void create(Order order) {
        self.save(order); // 走代理,事务生效
    }
}

// 方案2:从Spring上下文获取
public void create(Order order) {
    OrderService proxy = applicationContext.getBean(OrderService.class);
    proxy.save(order);
}

// 方案3:把事务方法放到另一个Service
@Service
public class OrderService {
    @Autowired
    private SaveService saveService;
    
    public void create(Order order) {
        saveService.save(order); // 跨Service调用
    }
}

面试官想听:

  • 知道事务失效的根本原因(代理 vs 真实对象)
  • 能给出至少2种解决方案
  • 最好能说出"项目中遇到过,用方案1解决的"

总结:小厂面试官的心理预期

场景题 能过的最低标准 加分项
下单扣库存 知道事务加在Service、会回滚 高并发优化(乐观锁)、rollbackFor
批量导入 知道大事务有问题 能说两种需求的不同方案、REQUIRES_NEW
内部调用失效 知道this.调用不生效 给出3种解法、能说原理(代理)
相关推荐
2301_816660212 小时前
Bootstrap框架的最小宽度限制是多少
jvm·数据库·python
c***89202 小时前
在 Ubuntu 上安装 MySQL 的详细指南
mysql·ubuntu·adb
少许极端2 小时前
算法奇妙屋(四十八)-单调栈
java·算法·单调栈
OtIo TALL2 小时前
SQL-触发器(trigger)的详解以及代码演示
服务器·数据库·sql
学习使我健康2 小时前
Android 本地音乐播放(读取系统媒体库 + MediaPlayer)
java·android-studio
lwx572802 小时前
MySQL 数据库自动化备份脚本:从入门到生产实践
数据库·后端
abc123456sdggfd2 小时前
HTML5中Vuex持久化插件中WebStorage的底层配置
jvm·数据库·python
pele2 小时前
Go语言如何发GET请求_Go语言HTTP GET请求教程【总结】
jvm·数据库·python