事务处理
COMMIT / ROLLBACK / SAVEPOINT / ACID / 事务隔离
什么是事务
一句话:事务是一组 SQL,要么全成功,要么全撤销。
举个经典场景——转账:张三转 1000 给李四,需要两步:
UPDATE users SET salary = salary - 1000 WHERE name = '张三';
UPDATE users SET salary = salary + 1000 WHERE name = '李四';如果执行完第一步后系统崩了(停电、死机、网络断了),张三扣了 1000,李四没收到——钱凭空消失了。
事务就是为了防止这种情况:
START TRANSACTION;
UPDATE users SET salary = salary - 1000 WHERE name = '张三';
UPDATE users SET salary = salary + 1000 WHERE name = '李四';
COMMIT; -- 两条都成功,一起生效如果中间出问题,不提交,而是回滚:
START TRANSACTION;
UPDATE users SET salary = salary - 1000 WHERE name = '张三';
-- 第二步失败了
ROLLBACK; -- 撤销,张三的钱恢复原样COMMIT 和 ROLLBACK
COMMIT:确认提交
START TRANSACTION;
DELETE FROM orders WHERE user_id = 5;
COMMIT; -- 确认删除,写入磁盘COMMIT 之后,改动永久生效,无法回滚。
ROLLBACK:撤销回滚
START TRANSACTION;
DELETE FROM orders WHERE user_id = 5;
-- 删错了!或者检查后发现不对
ROLLBACK; -- 撤销,数据恢复ROLLBACK 之后,这个事务里的所有操作就像没发生过一样。
何时自动提交
MySQL 默认开启自动提交模式——每条 SQL 执行完立即生效,你不用写 COMMIT:
-- 这条 UPDATE 一跑完就写入磁盘了,没法回滚
UPDATE users SET salary = 10000 WHERE name = '张三';想手动控制事务,要么用 START TRANSACTION 显式开启,要么关掉自动提交:
SET autocommit = 0; -- 关掉自动提交(这个会话内有效)
SET autocommit = 1; -- 恢复SAVEPOINT:部分回滚
有时候不想回滚整个事务,只想退回几步:
START TRANSACTION;
UPDATE users SET salary = 12000 WHERE name = '张三';
SAVEPOINT step1; -- 在这里设个保存点
UPDATE users SET city = '成都' WHERE name = '张三';
SAVEPOINT step2; -- 再设一个
UPDATE users SET age = 30 WHERE name = '张三';
-- 发现最后一步不对,只想回退到 step2
ROLLBACK TO step2;
COMMIT;结果:salary 和 city 改成功了,age 被撤销。step1 和 step2 之间的改动保留。
💡 保存点在多步操作的调试中很有用——出错的步骤单独回退,不连累前面的。
ACID:事务的四个特性
这是事务的理论根基,但不需要死记——理解就行:
| 特性 | 含义 | 大白话 |
|---|---|---|
| 原子性 (Atomicity) | 一个事务要么全做,要么全不做 | 转账不能只扣不加 |
| 一致性 (Consistency) | 事务结束时,数据满足所有约束 | 钱不会凭空消失 |
| 隔离性 (Isolation) | 多个事务同时跑,互不干扰 | 两个人同时转账不出乱子 |
| 持久性 (Durability) | 一旦提交,数据永久保存 | 提交后停电数据也不丢 |
记住一个词 ACID 就够了,面试问到能解释每个字母代表什么就行。
事务隔离级别
多个事务同时运行时,可能出现三个经典问题:
脏读
读到别人还没提交的数据。
事务 A:张三 -1000,还没 COMMIT
事务 B:查余额 → 看到了扣款后的数字
事务 A:ROLLBACK 撤销了
事务 B 看到的数据是"脏"的——根本没发生过不可重复读
同一个事务内,两次读同一行,结果不一样。
事务 A:读张三余额 → 10000
事务 B:改张三余额为 5000,COMMIT
事务 A:再读张三余额 → 5000(变了!)幻读
同一个事务内,两次 SELECT 同一条件,行数不一样。
事务 A:查北京用户 → 3 行
事务 B:插入一个新北京用户,COMMIT
事务 A:再查北京用户 → 4 行(多了一行"幻影")四种隔离级别
MySQL InnoDB 默认是可重复读 (REPEATABLE READ),一般不用改:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| 读未提交 | ✅ 会 | ✅ 会 | ✅ 会 | 最快 |
| 读已提交 | ❌ 防 | ✅ 会 | ✅ 会 | 较快 |
| 可重复读 ⬅ 默认 | ❌ 防 | ❌ 防 | 基本防 | 中等 |
| 串行化 | ❌ 防 | ❌ 防 | ❌ 防 | 最慢 |
查看和设置:
-- 查看当前隔离级别
SELECT @@transaction_isolation; -- MySQL 8.0+
-- REPEATABLE-READ
-- 改隔离级别(当前会话)
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;💡 实际开发中几乎不需要调隔离级别——默认的可重复读已经覆盖了绝大多数场景。记住有这回事就行,碰到了再查。
只有 InnoDB 支持事务
建表时注意引擎:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20)
) ENGINE=InnoDB; -- 支持事务
-- 如果用了 MyISAM,START TRANSACTION 和 ROLLBACK 都没用。查看表的引擎:
SHOW TABLE STATUS WHERE Name = 'users';一个好的习惯
改数据之前先开事务,确认无误再提交:
START TRANSACTION;
-- 1. 先 SELECT 确认目标行
SELECT id, name, salary FROM users WHERE salary < 6000;
-- 2. 执行修改
DELETE FROM users WHERE salary < 6000;
-- 3. 再 SELECT 确认结果
SELECT id, name, salary FROM users WHERE salary < 6000;
-- 4. 没问题就提交
COMMIT;
-- 有问题就 ROLLBACK;小结
事务是数据库安全感的来源——要么全成功,要么全撤销:
START TRANSACTION开事务 — 手动控制一组 SQL 的提交和撤销COMMIT确认,ROLLBACK撤销 — 提交后永久生效,回滚后像什么都没发生SAVEPOINT部分回滚 — 不用从头撤销,退到保存点就行- ACID 记住四个词 — 原子性、一致性、隔离性、持久性,面试必问
- InnoDB 才支持事务 — MyISAM 开事务也没用,建表时确认引擎
💡 好习惯:改数据前
START TRANSACTION→SELECT确认 → 执行修改 →SELECT再确认 → 没问题COMMIT,有问题ROLLBACK。
自主练习
- 开一个事务,修改自己的工资,然后 ROLLBACK,确认数据恢复了
- 开一个事务,用 SAVEPOINT 实现两步修改,只回退第二步
- 查看你的 MySQL 当前使用的隔离级别
- 思考:为什么转账必须用事务?如果用两条独立的 UPDATE 会出现什么问题?