事务是数据库系统的核心概念之一它保证了数据在并发操作和系统故障下的正确性。无论是面试中的高频考点还是日常开发中的Bug源头理解事务都至关重要。本文将系统性地讲解事务的四大特性ACID、并发问题、隔离级别并结合MySQL InnoDB引擎分析底层实现原理最后给出最佳实践建议。一、什么是事务事务Transaction是数据库管理系统执行过程中的一个逻辑单位由一个有限的数据库操作序列构成。简单来说事务就是将一组操作捆绑在一起要么全部成功要么全部失败回滚。一个经典场景银行转账-- 账户A给账户B转账100元 START TRANSACTION; UPDATE account SET balance balance - 100 WHERE user_id A; UPDATE account SET balance balance 100 WHERE user_id B; COMMIT;如果没有事务第一条语句执行后系统崩溃就会导致A的钱扣了B却没收到数据不一致。二、事务的四大特性ACID特性英文含义保证手段原子性Atomicity事务中的所有操作要么全部成功要么全部失败回滚undo log回滚日志一致性Consistency事务执行前后数据库从一个一致性状态变到另一个一致性状态原子性、隔离性、持久性共同保证隔离性Isolation并发执行的事务之间互不干扰锁机制 MVCC持久性Durability事务提交后数据的修改是永久性的redo log重做日志1. 原子性undo log 的作用InnoDB 通过undo log实现原子性。当事务需要回滚时根据 undo log 中的记录将数据恢复到修改前的状态。每个写操作INSERT/UPDATE/DELETE都会生成对应的 undo log。UPDATE 操作会记录旧值回滚时执行反向操作。2. 持久性redo log 的作用InnoDB 通过redo log保证持久性。事务提交时并不会立即将数据刷入磁盘的数据文件而是先写入 redo log顺序写性能高再异步刷盘。即使数据库崩溃重启后可以根据 redo log 重放已提交事务的修改保证数据不丢失。这就是WALWrite-Ahead Logging技术。三、并发事务带来的问题当多个事务同时执行时如果不加以隔离会导致以下问题问题描述示例脏读一个事务读取到另一个未提交事务修改的数据事务B修改了数据但未提交事务A读取到该修改事务B回滚后事务A读到的就是脏数据不可重复读同一个事务内两次读取同一行数据的结果不同事务A第一次读取某行事务B修改并提交了该行事务A第二次读取时发现数据变了幻读同一个事务内两次查询返回的记录条数不同事务A查询符合条件的记录数事务B插入了一条新记录并提交事务A再次查询时多了一条区别说明不可重复读针对数据更新UPDATE/DELETE关注的是同一行数据的内容变化。幻读针对数据新增INSERT关注的是记录数量的变化。四、事务隔离级别SQL 标准定义了四种隔离级别用来解决上述并发问题。MySQL InnoDB 默认使用可重复读REPEATABLE READ。隔离级别脏读不可重复读幻读实现原理READ UNCOMMITTED读未提交✔✔✔不加锁直接读取最新数据READ COMMITTED读已提交✘✔✔每次读取都生成新的快照MVCCREPEATABLE READ可重复读✘✘✘*第一次读取时生成快照整个事务共用MVCC 间隙锁SERIALIZABLE可串行化✘✘✘所有读操作加锁读锁 写锁完全串行执行*注MySQL InnoDB 在可重复读级别下通过间隙锁Gap Lock部分解决了幻读问题但严格意义上的幻读如SELECT ... FOR UPDATE场景仍需注意。隔离级别实战查看与设置-- 查看当前隔离级别 SELECT transaction_isolation; -- 或 SHOW VARIABLES LIKE transaction_isolation; -- 设置隔离级别会话级别 SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 设置隔离级别全局级别 SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;五、MySQL InnoDB 隔离级别底层实现1. MVCC多版本并发控制InnoDB 通过MVCC实现非锁定一致性读避免读写冲突。每行数据背后隐藏着两个字段DB_TRX_ID最后修改的事务ID和DB_ROLL_PTR回滚指针指向 undo log。读操作时根据Read View活跃事务列表判断应该读取哪个版本的数据。READ COMMITTED每次 SELECT 都重新生成 Read View。REPEATABLE READ第一次 SELECT 时生成 Read View整个事务期间复用。2. 锁机制锁类型描述使用场景共享锁S锁允许事务读禁止其他事务写SELECT ... LOCK IN SHARE MODE排他锁X锁禁止其他事务读和写SELECT ... FOR UPDATE、UPDATE、DELETE、INSERT意向锁表级锁表示事务打算在行上加共享锁或排他锁InnoDB 自动管理用于提高锁兼容性判断效率间隙锁锁定记录之间的间隙防止幻读可重复读级别下范围查询或唯一索引等值查询不存在时使用临键锁记录锁 间隙锁锁定一个左开右闭的区间InnoDB 默认的行锁算法3. 可重复读如何解决幻读-- 场景用户表id 为主键现有 id 1, 2, 5 SELECT * FROM user WHERE id 2 FOR UPDATE;在可重复读级别下InnoDB 会对(2, ∞)范围加上临键锁其他事务无法插入id 3、id 4等记录从而避免幻读。六、Spring 事务管理在实际开发中我们通常使用 Spring 框架来管理事务。1. 声明式事务TransactionalService public class OrderService { Autowired private OrderMapper orderMapper; Transactional(rollbackFor Exception.class) public void createOrder(Order order) { orderMapper.insert(order); // 模拟异常 // if (true) throw new RuntimeException(模拟异常); // 发生异常时自动回滚 } }2. Transactional 失效场景场景原因解决方案同一个类内方法调用Spring AOP 代理失效未经过代理类注入自身或使用AopContext.currentProxy()方法非 publicSpring 事务代理只对 public 方法生效改为 public 方法异常被 try-catch 捕获事务管理器感知不到异常在 catch 中手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()数据库引擎不支持事务MySQL MyISAM 引擎不支持事务使用 InnoDB 引擎3. 事务传播行为PropagationSpring 定义了七种事务传播行为最常用的有三种传播行为描述REQUIRED支持当前事务如果不存在则新建默认REQUIRES_NEW挂起当前事务新建一个独立的事务NESTED在当前事务中创建嵌套事务子事务回滚不影响外部事务javaTransactional(propagation Propagation.REQUIRES_NEW) public void handleLog() { // 日志记录独立事务即使外部主事务回滚日志也会保留 }七、最佳实践与避坑指南1. 事务粒度要小// 错误示例事务中包含了非数据库操作 Transactional public void processOrder() { // 远程调用可能耗时很长导致数据库连接占用 httpClient.callExternalApi(); // 数据库操作 orderMapper.update(); } // 正确示例缩小事务范围 public void processOrder() { httpClient.callExternalApi(); // 放在事务外 doDbOperation(); } Transactional public void doDbOperation() { orderMapper.update(); }2. 合理设置超时时间Transactional(timeout 30) // 单位秒 public void slowMethod() { // 超过30秒未提交则自动回滚 }3. 指定回滚异常类型Spring 默认只对RuntimeException和Error回滚受检异常Exception子类不会回滚。// 指定所有异常都回滚 Transactional(rollbackFor Exception.class)4. 避免分布式事务的陷阱微服务架构下单个Transactional无法跨多个数据源。解决方案最终一致性消息队列 本地消息表Seata 等分布式事务框架尽量避免跨库事务从设计层面解耦八、总结核心概念要点ACID原子性靠 undo log持久性靠 redo log隔离性靠 MVCC 锁隔离级别MySQL 默认可重复读Oracle 默认读已提交并发问题脏读、不可重复读、幻读隔离级别越高考古问题越少并发性能越差Spring 事务注意失效场景合理设置传播行为和粒度一句话总结事务是数据库一致性的基石理解其原理才能写出健壮的代码。