MySQL与Spring,事务与自动提交有什么关系?
- 一、MySQL事务
-
- [1. MySQL事务的开启与结束](#1. MySQL事务的开启与结束)
- [2. 自动提交与事务的关系](#2. 自动提交与事务的关系)
-
- [2.1 自动提交的机制](#2.1 自动提交的机制)
- [2.2 自动提交与会话](#2.2 自动提交与会话)
- [2.3 `BEGIN`/`START TRANSACTION`与自动提交的关系](#2.3
BEGIN/START TRANSACTION与自动提交的关系)
- 二、Spring事务管理机制深度解析
-
- [1. Spring事务管理架构](#1. Spring事务管理架构)
- [2. Spring事务中setAutoCommit(false)的本质](#2. Spring事务中setAutoCommit(false)的本质)
-
- [2.1 Spring事务管理器源码简介](#2.1 Spring事务管理器源码简介)
- [2.2 `setAutoCommit(false)` ≠ 开启事务](#2.2
setAutoCommit(false)≠ 开启事务) - [2.3 为什么需要`setAutoCommit(false)`?](#2.3 为什么需要
setAutoCommit(false)?) - [2.4 为什么Spring不直接使用START TRANSACTION?](#2.4 为什么Spring不直接使用START TRANSACTION?)
- [3. Spring事务与MySQL事务的对应关系](#3. Spring事务与MySQL事务的对应关系)
- [4. Spring事务传播行为与连接绑定](#4. Spring事务传播行为与连接绑定)
- [5. Spring事务的完整生命周期](#5. Spring事务的完整生命周期)
- 三、总结与最佳实践
-
- [1. 核心要点总结](#1. 核心要点总结)
- [2. 性能与注意事项](#2. 性能与注意事项)
双生子文章 :MySQL事务与Spring事务的关系
一、MySQL事务
1. MySQL事务的开启与结束
MySQL开启事务的方式
- 显式开启事务 :使用
STRAT TRANSACTION或BEGIN显式的开启事务,COMMIT 或 ROLLBACK 来控制事务的结束。 - 隐式事务 :隐式事务指的是,在
关闭自动提交(SET autocommit = 0)模式后,后续SQL语句默认属于同一个事务。SET autocommit = 0 命令不是直接开启事务的命令,而是设置事务模式。 事务开启的"本质"是关闭自动提交,并非执行 START TRANSACTION,START TRANSACTION 是显式标记一个事务的开始,而关闭自动提交时让数据库知道"接下来的SQL语句需要一起提交"。
事务结束命令
COMMIT:提交事务,让所有修改永久生效。ROLLBACK:回滚事务,撤销所有未提交的修改。- 隐式提交:某些语句(如DDL:CREATE、ALTER、DROP等)会自动提交当前事务。
命令对比
sql
-- 方式1:使用START TRANSACTION
START TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;// 提交事务
-- 方式2:使用BEGIN
BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
-- 方式3:START TRANSACTION 支持的特性选项
START TRANSACTION READ ONLY; -- 开启只读事务
START TRANSACTION READ WRITE; -- 开启读写事务(默认)
START TRANSACTION WITH CONSISTENT SNAPSHOT; -- 开启一致性快照事务(InnoDB)
-- 方式4:通过设置自动提交模式隐式开启事务
-- 4.1 查看当前自动提交状态
SHOW VARIABLES LIKE 'autocommit';
-- 4.2 关闭自动提交(默认为ON/1)
SET autocommit = 0; -- 或 SET autocommit = OFF;
-- 此时每条SQL语句都不会自动提交,需要显式COMMIT
UPDATE accounts SET balance = 1000 WHERE id = 1;
UPDATE accounts SET balance = 2000 WHERE id = 2;
COMMIT;// 提交事务
-- 恢复自动提交
SET autocommit = 1;
2. 自动提交与事务的关系
2.1 自动提交的机制
默认状态 :MySQL默认开启自动提交(autocommit = 1),也就是每条SQL语句在执行后会立即自动提交。
自动提交的含义:
sql
-- 当 autocommit=1(默认开启自动提交) 时,下面每条语句都是一个独立的事务
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 自动提交
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 自动提交
-- 当 autocommit=0 时,需要显式提交
SET autocommit = 0;// 关闭自动提交
UPDATE accounts SET balance = balance - 100 WHERE id = 1; -- 未提交
UPDATE accounts SET balance = balance + 100 WHERE id = 2; -- 未提交
COMMIT; -- 手动提交
2.2 自动提交与会话
MySQL的【自动提交】默认是开启的,不过我们也可以在配置文件中进行全局的设置。我们这里说的【自动提交】是 会话级别 的设置:
- 每个数据库连接(Connection)都有自己的自动提交状态。
- 连接的自动提交状态不会影响其他连接。
- 连接关闭后重新建立,会恢复为默认的自动提交状态。
2.3 BEGIN/START TRANSACTION与自动提交的关系
sql
-- 即使 autocommit = 1,使用BEGIN也会暂时关闭自动提交
SET autocommit = 1; -- 默认状态:开启自动提交模式。
BEGIN; -- 此时自动提交被暂时挂起
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
-- 这里不会自动提交
COMMIT; -- 提交事务,提交后自动提交状态恢复为1
-- 如果没有BEGIN,直接执行UPDATE会立即自动提交
UPDATE accounts SET balance = 1000 WHERE id = 1; -- 立即提交
二、Spring事务管理机制深度解析
1. Spring事务管理架构
┌──────────────────────────────────────────────────────────────────┐
│ Spring Transaction Management │
├──────────────────────────────────────────────────────────────────┤
│ @Transactional(声明式事务) / TransactionTemplate(编程式事务) │
├──────────────────────────────────────────────────────────────────┤
│ PlatformTransactionManager (事务管理器接口) │
│ ├─ DataSourceTransactionManager (实现) │
│ ├─ JpaTransactionManager │
│ └─ JtaTransactionManager │
├──────────────────────────────────────────────────────────────────┤
│ DataSource (数据源) / │
│ Connection Pool (数据库连接池,比如,Druid, HikariCP) │
├──────────────────────────────────────────────────────────────────┤
│ JDBC Connection │
└──────────────────────────────────────────────────────────────────┘
2. Spring事务中setAutoCommit(false)的本质
2.1 Spring事务管理器源码简介
我们可以从 编程式事务:【事务模板TransactionTemplate】的核心方法 execute(TransactionCallback action) 看起:
java
// Spring DataSourceTransactionManager 源码关键片段(有部分替换,剔除掉了复杂的调用过程)
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager {
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
// 1. 从连接池获取连接
con = this.dataSource.getConnection();
// 2. 关闭自动提交 - 核心代码!
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
// 【核心】:就是在这里去告诉MySQL不要自动提交,而是需要手动控制事务。
con.setAutoCommit(false);
}
// 3. 设置隔离级别(如果需要)
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(
con, definition);
// 4. 其他配置...
}
catch (Throwable ex) {
// 异常处理
}
}
}
对于上面的源码,Spring事务管理器(DataSourceTransactionManager)在获取到数据库连接后执行的 connection.setAutoCommit(false),就是在告诉 MySQL "不要自动提交,需要手动控制事务"。
1. 为什么是这样?
- MySQL默认行为:MySQL默认自动提交,每条SQL语句独立提交。
事务需求:++为了实现ACID中的原子性,需要将多个SQL语句视为一个整体++。- 实现方式:通过关闭自动提交,使后续SQL语句不立即生效,而是等待COMMIT才永久生效。
2. 技术本质:setAutoCommit(false) 是 Java JDBC API 对 MySQL 的 SET autocommit = 0命令 的封装,是 Spring事务管理 实现的核心机制。它告诉数据库"接下来的 SQL语句 属于同一个事务,不要自动提交"。
2.2 setAutoCommit(false) ≠ 开启事务
代码层面的理解
java
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false); // 这只是准备阶段!
// 此时MySQL中"事务"并没有被开启
// 直到执行第一条SQL语句,MySQL才真正开始事务
conn.createStatement().executeUpdate(
"UPDATE accounts SET balance = balance - 100 WHERE id = 1");
// ↑ 执行这第一条SQL时,MySQL才真正开始事务
// 后续SQL都在同一个事务中
conn.createStatement().executeUpdate(
"UPDATE accounts SET balance = balance + 100 WHERE id = 2");
conn.commit(); // 提交事务
MySQL的实际行为
sql
-- 当Spring执行 setAutoCommit(false) 时,MySQL层面:
-- 1. 只是设置了连接的属性,没有 start transaction / begin 语句
-- 2. 连接处于 "非自动提交" 模式。
-- 当执行第一条INSERT/UPDATE/DELETE时,MySQL自动开始事务(隐式开始)
-- 相当于自动执行了 start transaction / begin
-- 执行commit / rollback 时,事务结束。
2.3 为什么需要setAutoCommit(false)?
java
// 如果不设置 setAutoCommit(false):
Connection conn = dataSource.getConnection(); // 默认 autocommit=true
// 每条SQL都会立即提交,无法回滚
conn.createStatement().executeUpdate("UPDATE ..."); // 立即提交!
conn.createStatement().executeUpdate("UPDATE ..."); // 立即提交!
// 如果第二条语句失败,第一条已经提交,无法回滚
// 原子性被破坏!
// 有了 setAutoCommit(false):
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false); // 关闭自动提交
conn.createStatement().executeUpdate("UPDATE ..."); // 未提交
conn.createStatement().executeUpdate("UPDATE ..."); // 未提交
// 如果第二条失败,可以回滚第一条
conn.rollback(); // 事务回滚,两条都撤销
2.4 为什么Spring不直接使用START TRANSACTION?
Spring 不直接使用 START TRANSACTION 命令,而是通过 关闭自动提交 来实现事务,这是因为:
- 兼容性:关闭自动提交是JDBC标准API,适用于所有数据库。
- 灵活性:可以控制事务边界,而不仅仅是开始一个事务。
- 性能:避免了额外的SQL语句开销。
3. Spring事务与MySQL事务的对应关系
Spring事务生命周期 MySQL层面发生的事情
─────────────────────────────────────────────────────────────
@Transactional方法开始
↓
DataSourceTransactionManager.doBegin()
- 获取Connection
- conn.setAutoCommit(false) → MySQL: autocommit=0(仅设置)
↓
业务方法执行
- 第一条SQL执行 → MySQL: 隐式 start transaction(事务真正开始)
- 后续SQL执行 → MySQL: 在事务中执行
↓
方法正常结束
- DataSourceTransactionManager.doCommit()
- conn.commit() → MySQL: COMMIT(提交事务)
↓
连接清理
- 恢复autocommit=true → MySQL: autocommit=1(恢复默认)
- 连接归还连接池
4. Spring事务传播行为与连接绑定
java
// 1. Spring通过ThreadLocal管理连接,实现传播行为
public abstract class TransactionSynchronizationManager {
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
// 连接被绑定到当前线程
public static void bindResource(Object key, Object value) {
// ...
}
// 从当前线程获取连接
public static Object getResource(Object key) {
// ...
}
}
// 2. 传播行为示例
@Component
@Transactional
public class AccountComponent {
@Resource
private AccountService accountService;
public void methodA() {
// 开启新事务,获取新连接,setAutoCommit(false)
this.accountService.methodB(); // 会加入methodA的事务
// 提交事务
}
}
@Service
public class AccountService {
@Transactional(propagation = Propagation.REQUIRED) // 默认事务传播行为
public void methodB() {
// 检查当前线程已有事务,加入已有事务
// 不会执行setAutoCommit(false),因为已关闭自动提交
// 使用methodA的连接
}
}
@Service
public class TradeService {
@Transactional(propagation = Propagation.REQUIRES_NEW) // 注意看这里的事务传播行为
public void methodC() {
// 即使当前线程已有事务,也开启新事务
// 获取新连接,setAutoCommit(false)
// 创建新的事务(MySQL层面的新事务)
}
}
5. Spring事务的完整生命周期
java
// Spring事务的完整控制流程
@Component
public class AccountService {
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
// 1. Spring AOP拦截方法调用
// 2. 创建TransactionStatus
// 3. DataSourceTransactionManager.doBegin()被调用
// doBegin内部:
// - 获取数据库连接
// - setAutoCommit(false) ← 这里!!!!!!
// - 设置隔离级别
// - 将连接绑定到ThreadLocal ← 这里!!!!!!
// 4. 执行业务方法
accountRepository.decreaseBalance(fromId, amount);
accountRepository.increaseBalance(toId, amount);
// 5. 方法结束,如果没有异常:
// DataSourceTransactionManager.doCommit()被调用
// - 执行 connection.commit()
// 6. 如果有异常:
// DataSourceTransactionManager.doRollback()被调用
// - 执行 connection.rollback()
// 7. 清理:
// - 恢复连接的autoCommit状态(将其设置为true) ← 这里!!!!!!
// - 从ThreadLocal解绑连接 ← 这里!!!!!!
// - 关闭连接(实际是还给连接池)
}
}
三、总结与最佳实践
1. 核心要点总结
setAutoCommit(false)不是开启MySQL事务的命令,而是 准备连接以支持事务。- 真正的MySQL事务 在执行第一条SQL时隐式开始(或通过 start transaction 显式开始)。
- Spring事务管理 是JDBC事务的增强封装,提供了声明式、编程式事务管理。
- 连接与线程绑定 是Spring实现事务传播行为的关键机制。
2. 性能与注意事项
- 连接获取成本:频繁开启事务会增加连接获取成本。
- 事务粒度:尽量保持事务短小,避免长事务。
- 自动提交恢复:Spring会正确恢复连接的自动提交状态,但自定义事务管理时需要注意。
- 连接泄漏:确保事务结束后的连接正确释放。
通过深入理解Spring事务与MySQL事务的交互机制,我们可以更好地设计事务策略,避免常见的事务问题,并优化系统性能。