什么是事务?
事务是一个工作单元,它是一系列操作的集合,这些操作要么全部成功执行,要么全部失败回滚,不会出现部分成功部分失败的情况,事务确保了数据的一致性 和 完整性。

(图片来源网络,侵删)
一个经典的例子就是银行转账:
- 从 A 账户扣款。
- 向 B 账户存款。
这两个操作必须作为一个整体,如果第一步成功但第二步失败(例如系统崩溃),A 的钱就凭空消失了,事务可以保证在这种异常情况下,A 的钱会自动退回。
事务的 ACID 特性
理解事务通常从它的四个核心特性开始:
- 原子性:事务是一个不可分割的工作单元,事务中的操作要么全部发生,要么全部不发生。
- 一致性:事务必须使数据库从一个一致性状态变换到另一个一致性状态,转账前后,A 和 B 的总金额是不变的。
- 隔离性:多个并发的事务之间是相互隔离的,一个事务的执行不能被其他事务干扰,即,一个事务内部的操作及所需的数据对并发的其他事务是隔离的。
- 持久性:一旦事务被提交,它对数据库中数据的改变就是永久性的,即使数据库发生故障(如断电、宕机),数据库也能恢复到事务成功结束时的状态。
MySQL 事务的核心命令
开始事务
命令: START TRANSACTION; 或 BEGIN;

(图片来源网络,侵删)
- 作用: 显式地开启一个事务,从这条语句执行开始,之后的 SQL 语句都将在这个事务中执行,直到遇到
COMMIT或ROLLBACK。 - 注意: 如果不使用
START TRANSACTION,那么每条 SQL 语句都会在执行后被自动提交(即autocommit=1的状态)。
START TRANSACTION; -- 或者 BEGIN;
提交事务
命令: COMMIT;
- 作用: 提交当前事务,它将事务中所有对数据库的修改永久保存到数据库中,一旦提交,事务结束,
autocommit模式会重新生效。 - 效果: 在
COMMIT执行之前,其他会话是看不到这些修改的(隔离性保证)。COMMIT之后,修改对所有会话可见。
-- 执行一系列操作... UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; UPDATE accounts SET balance = balance + 100 WHERE user_id = 2; -- 确认无误后,提交事务 COMMIT;
回滚事务
命令: ROLLBACK;
- 作用: 回滚当前事务,它会撤销自
START TRANSACTION或BEGIN之后所有未提交的 SQL 操作,将数据库恢复到事务开始之前的状态。 - 使用场景: 当事务执行过程中发生错误,或者业务逻辑判断需要取消操作时,使用
ROLLBACK来保证数据的一致性。
START TRANSACTION; UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- 在这里假设发生了错误,或者业务逻辑不满足 -- ... -- 回滚所有操作 ROLLBACK;
设置保存点
命令: SAVEPOINT savepoint_name;
- 作用: 在当前事务中设置一个保存点,保存点是一个标记,你可以将事务回滚到这个标记点,而不是回滚到事务的开始。
- 优点: 相比于只能全部回滚的
ROLLBACK,保存点提供了更细粒度的回滚控制。
START TRANSACTION; -- 操作1 DELETE FROM orders WHERE order_id = 101; SAVEPOINT sp1; -- 设置保存点 sp1 -- 操作2 DELETE FROM customers WHERE customer_id = 501; SAVEPOINT sp2; -- 设置保存点 sp2 -- 如果发现操作2有问题,但操作1是正确的 -- 可以只回滚到 sp2,撤销操作2 ROLLBACK TO sp2; -- 如果确认所有操作都没问题,再提交整个事务 COMMIT;
释放保存点
命令: RELEASE SAVEPOINT savepoint_name;

(图片来源网络,侵删)
- 作用: 删除(释放)一个之前设置的保存点,当保存点不再需要时,可以释放它。
- 注意: 当事务提交或回滚(非回滚到保存点)时,所有在该事务中创建的保存点都会被自动释放。
-- ... (在事务中) RELEASE SAVEPOINT sp1;
一个完整的事务示例
下面是一个完整的银行转账事务示例,演示了 START TRANSACTION, COMMIT 和 ROLLBACK 的使用。
场景: 用户 A (ID=1) 给用户 B (ID=2) 转账 100 元。
-- 1. 查看初始余额 SELECT user_id, balance FROM accounts; -- 假设 A 的余额是 1000, B 的余额是 500 -- 2. 开始事务 START TRANSACTION; -- 3. 执行核心操作 -- 从 A 的账户扣款 UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- 检查是否扣款成功 (可选,但推荐) -- A 的余额不足,后续操作应该回滚 -- SELECT balance FROM accounts WHERE user_id = 1 FOR UPDATE; -- 使用 FOR UPDATE 锁定行 -- 向 B 的账户存款 UPDATE accounts SET balance = balance + 100 WHERE user_id = 2; -- 4. 模拟一个错误,以便演示 ROLLBACK -- 假设在这里我们检查到 A 的余额不足,或者业务逻辑不满足 -- 我们主动触发一个错误来回滚 -- SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = '余额不足,转账失败!'; -- 或者,我们手动决定回滚 -- ROLLBACK; -- 取消注释这行来演示回滚 -- 5. 如果一切正常,提交事务 -- COMMIT; -- 取消注释这行来演示提交 -- 6. 再次查看余额,验证结果 -- 如果执行了 COMMIT,A 的余额变为 900,B 的变为 600。 -- 如果执行了 ROLLBACK,余额将恢复到初始状态 (1000 和 500)。 SELECT user_id, balance FROM accounts;
与事务相关的系统变量
autocommit
- 作用: 控制是否自动提交。
autocommit是一个会话级别的变量。 - 默认值:
ON(1),这意味着每条单独的 DML 语句(INSERT,UPDATE,DELETE)执行完毕后,都会立即被提交。 - 如何查看:
SELECT @@autocommit;
- 如何设置:
-- 关闭自动提交 SET autocommit = 0; -- 开启自动提交 SET autocommit = 1;
- 重要提示: 当你使用
START TRANSACTION或BEGIN显式开启一个事务时,当前会话的autocommit会被临时设置为OFF,直到事务被COMMIT或ROLLBACK,autocommit会恢复到之前的值。
transaction_isolation
- 作用: 设置事务的隔离级别,决定了事务之间的可见性。
- 如何查看:
SELECT @@transaction_isolation;
- 如何设置 (会话级别):
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
- 常见隔离级别:
READ UNCOMMITTED: 最低级别,可以读到未提交的数据。READ COMMITTED(RC): 默认级别(MySQL 8.0.3+),只能读取到已提交的数据。REPEATABLE READ(RR): MySQL 的默认级别(8.0.3 之前),保证在同一个事务中多次读取同样数据的结果是一致的。SERIALIZABLE: 最高级别,强制事务串行执行,性能最低,但隔离性最强。
最佳实践
- 保持事务尽可能短: 事务开始后,应尽快执行完毕并提交或回滚,长时间运行的事务会锁定数据库资源,影响其他会话的性能。
- 明确使用
START TRANSACTION: 不要依赖autocommit,显式地开启和结束事务可以让代码逻辑更清晰。 - 在业务逻辑层处理错误: 不要仅仅依靠数据库的错误来回滚,在应用程序代码中,通过
try...catch等机制捕获异常,并在异常发生时调用ROLLBACK。 - 合理使用保存点: 对于复杂事务,使用保存点可以避免因一个小错误而回滚整个事务,提高灵活性。
- 注意锁的使用: 在事务中,特别是查询和更新数据时,要小心锁,使用
SELECT ... FOR UPDATE可以锁定查询的行,防止其他事务在当前事务结束前修改它们,这有助于保证数据一致性,但也可能导致死锁。
