事务是数据库系统中保证数据一致性的核心机制,MySQL 的事务功能主要围绕 InnoDB 存储引擎实现。以下从基础概念到高级特性全面解析 MySQL 事务。
一、事务基础
1. ACID 特性
特性 | 描述 | MySQL 实现机制 |
原子性 (Atomicity) | 事务是不可分割的工作单位 | undo log 回滚 |
一致性 (Consistency) | 数据库从一个一致状态变到另一个一致状态 | 约束、触发器 |
隔离性 (Isolation) | 事务执行不受其他事务干扰 | 锁机制/MVCC |
持久性 (Durability) | 事务提交后永久生效 | redo log 持久化 |
2. 事务控制语句
START TRANSACTION; -- 或 BEGIN
INSERT INTO accounts VALUES (1, 1000);
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
COMMIT; -- 提交-- 出错时回滚
ROLLBACK;
二、事务隔离级别
1. 四种隔离级别
隔离级别 | 脏读 | 不可重复读 | 幻读 | 实现方式 |
读未提交 (READ UNCOMMITTED) | 可能 | 可能 | 可能 | 无锁 |
读已提交 (READ COMMITTED) | 不可能 | 可能 | 可能 | 记录锁 |
可重复读 (REPEATABLE READ) | 不可能 | 不可能 | 可能(InnoDB避免) | MVCC+间隙锁 |
串行化 (SERIALIZABLE) | 不可能 | 不可能 | 不可能 | 全表锁 |
2. 设置隔离级别
-- 查看当前隔离级别
SELECT @@transaction_isolation;-- 设置全局/会话级别
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
事务实现机制
1. MVCC (多版本并发控制)
核心组件:
- 隐藏字段:DB_TRX_ID(事务ID)、DB_ROLL_PTR(回滚指针)、DB_ROW_ID(行ID)
- ReadView:事务快照,决定可见哪些版本
工作流程:
- SELECT 时通过 ReadView 判断数据版本可见性
- UPDATE/DELETE 时创建新版本(写undo log)
- 旧版本通过回滚指针形成版本链
2. 锁机制
(1) 锁类型
锁类型 | 描述 |
共享锁(S锁) | 读锁,多个事务可同时持有 |
排他锁(X锁) | 写锁,独占资源 |
意向锁 | 表级锁,快速判断表中是否有行锁 |
(2) 行锁算法
锁算法 | 描述 | 解决什么问题 |
记录锁 | 锁定索引记录 | 避免写冲突 |
间隙锁 | 锁定索引记录间隙 | 防止幻读 |
临键锁 | 记录锁+间隙锁组合 | InnoDB默认 |
3. 日志系统
日志类型 | 作用 | 特点 |
redo log | 保证事务持久性 | 循环写入、物理日志 |
undo log | 实现事务回滚 | 逻辑日志、形成版本链 |
binlog | 主从复制 | 逻辑日志、追加写入 |
三、MySQL 事务传播机制详解
事务传播机制主要出现在 Spring 等框架管理的事务中,用于定义多个事务方法相互调用时事务应该如何传播。虽然原生 MySQL 不直接支持传播行为的概念,但在应用层通过框架可以实现。以下是全面解析:
一、Spring 事务传播类型
Spring 定义了 7 种传播行为(通过 @Transactional(propagation=...)
指定):
传播行为 | 说明 | 适用场景 |
REQUIRED (默认) | 如果当前存在事务,则加入该事务;否则新建一个事务 | 大多数业务场景 |
SUPPORTS | 如果当前存在事务,则加入该事务;否则以非事务方式执行 | 查询方法 |
MANDATORY | 必须在一个已有的事务中执行,否则抛出异常 | 强制要求事务的方法 |
REQUIRES_NEW | 新建事务,如果当前存在事务,则挂起当前事务 | 独立事务操作(如日志记录) |
NOT_SUPPORTED | 以非事务方式执行,如果当前存在事务,则挂起当前事务 | 不涉及数据一致性的操作 |
NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常 | 禁止事务的方法 |
NESTED | 如果当前存在事务,则在嵌套事务内执行;否则新建事务 | 部分可回滚的子操作 |
二、传播机制工作原理
1. REQUIRED (默认)
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {methodB(); // 加入methodA的事务
}@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {// 操作数据库
}
- 如果
methodB
抛出异常,methodA
也会回滚
2. REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {methodB(); // 挂起methodA的事务,新建独立事务// 即使methodB回滚,methodA可能继续执行
}@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {// 独立事务
}
3. NESTED (嵌套事务)
@Transactional
public void methodA() {try {methodB(); // 在嵌套事务中执行} catch (Exception e) {// 只回滚methodB的操作}// methodA的其他操作
}@Transactional(propagation = Propagation.NESTED)
public void methodB() {// 嵌套事务
}
- 嵌套事务使用保存点(SAVEPOINT)实现部分回滚
- 外层事务提交时,嵌套事务才会真正提交
三、MySQL 底层实现对应
虽然传播行为是应用层概念,但对应到 MySQL 底层机制:
传播行为 | MySQL 对应机制 |
REQUIRED | 共用同一个数据库连接/事务 |
REQUIRES_NEW | 新建数据库连接和事务 |
NESTED | 使用 SAVEPOINT 实现 |
NOT_SUPPORTED | 自动提交模式(auto-commit=1) |
四、传播机制实战案例
案例1:日志记录需要独立事务
public void placeOrder(Order order) {// 主业务逻辑orderDao.save(order); // REQUIRED传播try {logService.saveLog(order); // 日志需要独立事务} catch (Exception e) {// 日志失败不影响主业务}
}@Service
public class LogService {@Transactional(propagation = Propagation.REQUIRES_NEW)public void saveLog(Order order) {// 日志记录}
}
案例2:部分操作可回滚
@Transactional
public void processBatch(List<Item> items) {for (Item item : items) {try {processItem(item); // 单个item失败不影响其他} catch (Exception e) {// 记录错误继续处理}}
}@Transactional(propagation = Propagation.NESTED)
public void processItem(Item item) {// 处理单个item
}
五、传播机制选择建议
- 默认选择 REQUIRED
适用于大多数需要事务的业务方法 - REQUIRES_NEW 使用场景
-
- 审计日志记录
- 次要业务(如积分变更)不阻塞主业务
- 需要独立提交/回滚的操作
- NESTED 适用场景
-
- 批量处理中的单个项目处理
- 可部分回滚的业务流程
- 避免滥用 NOT_SUPPORTED/NEVER
除非明确知道不需要事务,否则可能破坏数据一致性
六、常见问题与解决方案
问题1:事务不生效
原因:
- 方法非 public
- 自调用(同一个类内方法调用)
- 异常被捕获未抛出
解决:
// 正确示例
@Transactional
public void methodA() {this.methodB(); // 仍然不生效!需通过代理对象调用
}// 解决方案1:注入自身代理
@Autowired
private MyService self; // 通过self调用// 解决方案2:使用AopContext
((MyService) AopContext.currentProxy()).methodB();
问题2:嵌套事务回滚异常
现象:
NESTED 传播的方法抛出异常,但外层事务未回滚
原因:
外层方法捕获了异常未继续抛出
解决:
@Transactional
public void outer() {try {inner(); // NESTED传播} catch (Exception e) {// 需要手动标记回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();throw e;}
}
七、性能考量
- REQUIRES_NEW 开销
-
- 需要新建数据库连接
- 适合低频操作(如日志)
- NESTED 与 MySQL
-
- 依赖 MySQL 的 SAVEPOINT 支持
- 比 REQUIRES_NEW 轻量
最佳实践
// 高频调用方法避免REQUIRES_NEW
@Transactional(propagation = Propagation.SUPPORTS)
public Data getData() {// 只读查询
}
通过合理选择传播机制,可以构建出既保证数据一致性又具备良好性能的事务体系。建议结合业务场景进行针对性设计。
四、事务实战应用
1. 转账事务示例
START TRANSACTION;
-- 检查账户存在
SELECT balance FROM accounts WHERE id = 1 FOR UPDATE;
-- 扣款
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 存款
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
2. 死锁处理
死锁场景:
-- 事务1
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;-- 事务2 (相反顺序)
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
解决方案:
- 统一资源访问顺序
- 设置锁等待超时
SET innodb_lock_wait_timeout = 30; -- 单位秒
- 自动检测回滚
SET innodb_deadlock_detect = ON; -- 默认开启
五、高级事务特性
1. 保存点(SAVEPOINT)
START TRANSACTION;
INSERT INTO orders VALUES(1, '2023-01-01', 100);
SAVEPOINT sp1;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
-- 可部分回滚
ROLLBACK TO SAVEPOINT sp1;
COMMIT;
2. 分布式事务(XA)
-- 协调者
XA START 'transaction_id';
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
XA END 'transaction_id';
XA PREPARE 'transaction_id';
XA COMMIT 'transaction_id'; -- 或 XA ROLLBACK
3. 事务性能优化
- 短事务:减少锁持有时间
- 合适隔离级别:避免过度使用SERIALIZABLE
- 索引优化:减少锁定范围
- 批量操作:减少事务次数
六、事务监控与问题排查
1. 查看当前事务
-- 查看运行中的事务
SELECT * FROM information_schema.INNODB_TRX;-- 查看锁等待
SELECT * FROM performance_schema.events_waits_current;
2. 长事务排查
-- 查找运行超过60s的事务
SELECT * FROM information_schema.INNODB_TRX
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;
3. 死锁日志分析
-- 查看最近死锁信息
SHOW ENGINE INNODB STATUS\G
-- 重点关注 "LATEST DETECTED DEADLOCK" 部分
七、InnoDB 事务参数配置
# 核心参数
innodb_flush_log_at_trx_commit = 1 # 1=最安全, 2=折衷, 0=性能
sync_binlog = 1 # 控制binlog刷盘
transaction-isolation = REPEATABLE-READ # 默认隔离级别# 性能相关
innodb_lock_wait_timeout = 50 # 锁等待超时(秒)
innodb_rollback_on_timeout = ON # 超时自动回滚
innodb_deadlock_detect = ON # 死锁检测
最佳实践建议
- 事务设计原则:
-
- 保持事务短小精悍
- 避免在事务中进行网络I/O
- 合理设置隔离级别
- 锁优化建议:
-
- 精确锁定需要的行(使用索引)
- 批量操作使用批量提交
- 高并发场景考虑乐观锁
- 异常处理:
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGINROLLBACK;-- 记录错误日志
END;
通过深入理解 MySQL 事务机制,可以构建出既保证数据一致性又具备高性能的数据库应用。建议结合业务场景选择合适的隔离级别和锁策略。