本文将带你深入理解MySQL事务机制,掌握Spring Boot中事务的正确使用方式,避免常见陷阱,提升代码质量。
想象一个银行转账操作:
如果第一步成功后系统崩溃,就会出现A账户已扣款但B账户未到账的数据不一致情况。事务正是为了解决这类问题而生。
特性 | 说明 | 现实比喻 |
原子性 | 事务是不可分割的最小单元 | 要么全部执行,要么全部回滚 |
一致性 | 数据库从一种一致状态转换到另一种一致状态 | 转账前后总金额不变 |
隔离性 | 并发事务之间相互隔离 | 多个转账操作互不干扰 |
持久性 | 事务提交后修改永久保存 | 转账成功记录不可丢失 |
-- 事务开始
START TRANSACTION;
UPDATE accounts SET balance = balance - 1000 WHERE user_id = 'A';
UPDATE accounts SET balance = balance + 1000 WHERE user_id = 'B';
-- 假设此时系统崩溃
COMMIT;实现机制:
// 模拟数据修改流程
public void updateData(Data data) {
// 1. 数据修改第一写入Buffer Pool(内存)
bufferPool.modify(data);
// 2. 同时记录redo log(磁盘)
redoLog.write(data.getChangeLog());
// 3. 定期将Buffer Pool刷回磁盘
bufferPool.flushToDisk();
}崩溃恢复:MySQL重启时,通过redo log重放未持久化的修改,确保数据不丢失。
问题类型 | 现象 | 解决方案 |
脏读 | 读到其他事务未提交的数据 | MVCC快照读 |
不可重复读 | 同一事务内多次读取结果不一致 | 版本控制 |
幻读 | 同一查询条件返回不同记录数 | Next-Key Lock |
-- 隐藏字段示例
ROW = {
id: 1,
name: "张三",
balance: 1000,
-- MySQL自动添加的隐藏字段
DB_TRX_ID: 123, -- 最后修改事务ID
DB_ROLL_PTR: 0x456 -- 指向上一个版本
}Read View机制:
class ReadView {
long m_low_limit_id; // 当前最大事务ID+1
long m_up_limit_id; // 活跃事务最小ID
Set<Long> m_ids; // 活跃事务ID列表
long m_creator_trx_id; // 创建该ReadView的事务ID
boolean isVisible(Record record) {
// 判断记录对当前事务是否可见
if (record.trx_id < m_up_limit_id) return true;
if (record.trx_id >= m_low_limit_id) return false;
return !m_ids.contains(record.trx_id);
}
}@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
@Service
@Transactional // 类级别事务,所有public方法默认开启事务
public class BankTransferService {
@Autowired
private AccountRepository accountRepository;
// 默认使用类级别的事务配置
public void transfer(String fromAccount, String toAccount, BigDecimal amount) {
Account from = accountRepository.findByAccountNumber(fromAccount);
Account to = accountRepository.findByAccountNumber(toAccount);
from.debit(amount); // 扣款
to.credit(amount); // 存款
accountRepository.save(from);
accountRepository.save(to);
}
// 自定义事务属性
@Transactional(
isolation = Isolation.READ_COMMITTED,
propagation = Propagation.REQUIRED,
timeout = 30,
rollbackFor = {BusinessException.class, InsufficientBalanceException.class}
)
public void complexTransfer(TransferRequest request) {
// 复杂的转账逻辑
}
}@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 主业务:创建订单
orderRepository.save(order);
// 调用其他服务方法,理解不同的传播行为
inventoryService.deductStock(order); // REQUIRED:加入当前事务
pointsService.addPoints(order); // REQUIRES_NEW:新建独立事务
notificationService.sendSms(order); // NOT_SUPPORTED:非事务执行
}
}
@Service
class InventoryService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductStock(Order order) {
// 库存扣减:独立事务,即使订单创建失败,库存扣减仍可提交
// 适用于需要独立保证成功的操作
}
}
@Service
class NotificationService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendSms(Order order) {
// 发送短信:不参与事务,无论订单是否创建成功都会发送
// 适用于非核心的辅助操作
}
}错误示例:
@Service
public class UserService {
public void updateUser(User user) {
// 自调用:事务失效!
updateBasicInfo(user);
updatePreferences(user);
}
@Transactional
public void updateBasicInfo(User user) {
// 这里的事务注解不会生效
userRepository.updateBasicInfo(user);
}
}解决方案:
@Service
public class UserService {
@Autowired
private UserService userService; // 注入代理对象
public void updateUser(User user) {
// 通过代理对象调用,事务生效
userService.updateBasicInfo(user);
userService.updatePreferences(user);
}
// 或者使用ApplicationContext
@Autowired
private ApplicationContext context;
public void updateUserV2(User user) {
UserService proxy = context.getBean(UserService.class);
proxy.updateBasicInfo(user);
}
}错误示例:
@Transactional
public void processOrder(Order order) {
try {
orderService.validate(order);
paymentService.process(order);
// 可能抛出受检异常
} catch (Exception e) {
log.error("处理失败", e);
// 异常被捕获,事务不会回滚!
}
}正确写法:
// 方案1:抛出运行时异常
@Transactional
public void processOrder(Order order) {
try {
// 业务逻辑
} catch (Exception e) {
log.error("处理失败", e);
throw new RuntimeException("订单处理失败", e); // 触发回滚
}
}
// 方案2:指定回滚异常类型
@Transactional(rollbackFor = Exception.class)
public void processOrder(Order order) throws BusinessException {
// 直接抛出受检异常也会触发回滚
orderService.validate(order);
}错误示例:
@Service
public class DataService {
@Transactional
protected void internalUpdate() { // 事务失效!
// 内部方法
}
}解决方案:
@Service
public class DataService {
public void businessOperation() {
// 调用public的事务方法
publicTransactionalMethod();
}
@Transactional
public void publicTransactionalMethod() {
// 事务逻辑
internalUpdate();
}
protected void internalUpdate() {
// 非事务性内部操作
}
}检查与解决方案:
-- 检查表引擎
SHOW TABLE STATUS LIKE 'your_table';
-- 修改为InnoDB引擎
ALTER TABLE your_table ENGINE=InnoDB;
-- 创建表时指定引擎
CREATE TABLE transaction_demo (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
data VARCHAR(255)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;正确配置:
@Configuration
public class MultiDataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public PlatformTransactionManager primaryTransactionManager() {
return new DataSourceTransactionManager(primaryDataSource());
}
@Bean
public PlatformTransactionManager secondaryTransactionManager() {
return new DataSourceTransactionManager(secondaryDataSource());
}
}
@Service
public class MultiSourceService {
@Transactional("primaryTransactionManager")
public void primaryOperation() {
// 使用主数据源事务
}
@Transactional("secondaryTransactionManager")
public void secondaryOperation() {
// 使用从数据源事务
}
}@Service
public class TransactionBestPractice {
// 原则1:事务范围尽可能小
@Transactional
public void optimizedTransfer(TransferRequest request) {
// 只包含必要的数据库操作
Account from = accountRepository.findById(request.getFromAccountId());
Account to = accountRepository.findById(request.getToAccountId());
// 业务计算放在事务外
BigDecimal actualAmount = calculateActualAmount(request.getAmount());
from.debit(actualAmount);
to.credit(actualAmount);
accountRepository.save(from);
accountRepository.save(to);
// 非数据库操作放在事务外
asyncSendNotification(request);
}
// 原则2:避免长事务
@Transactional(timeout = 30) // 设置合理超时时间
public void avoidLongTransaction() {
// 快速完成数据库操作
processCoreBusiness();
// 耗时操作放在事务外
CompletableFuture.runAsync(() -> {
heavyCalculation();
externalApiCall();
});
}
}日志配置:
# application.yml
logging:
level:
org.springframework.transaction: DEBUG
org.springframework.orm.jpa: DEBUG
org.hibernate.transaction: DEBUG
spring:
jpa:
show-sql: true
properties:
hibernate:
format_sql: true
generate_statistics: true事务监控工具:
@Component
public class TransactionMonitor {
public void monitorTransaction() {
try {
TransactionStatus status = TransactionAspectSupport.currentTransactionStatus();
System.out.println("事务状态: " + (status.isCompleted() ? "已完成" : "进行中"));
System.out.println("回滚标记: " + status.isRollbackOnly());
} catch (Exception e) {
System.out.println("当前不在事务中");
}
}
}通过本文的学习,你应该能够:
✅ 理解MySQL事务的底层实现机制
✅ 熟练使用Spring Boot事务
✅ 避免常见的事务陷阱
✅ 编写健壮的事务代码
记住:事务不是银弹,合理使用才能发挥最大价值。在分布式系统场景下,还需要思考分布式事务的解决方案,但这已经是另一个话题了。
希望这篇指南能协助你在日常开发中写出更健壮、更可靠的代码!