Skip to content

MySQL 第七章:事务与隔离级别

本章目标:理解事务为什么存在、并发下会出现哪些问题、隔离级别如何权衡,并掌握事务在业务开发中的正确使用方式。


1. 为什么需要事务

事务(Transaction)用于保证一组 SQL 要么全部成功、要么全部失败,常见于转账、下单、库存扣减等场景。

flowchart LR
    A[BEGIN] --> B[执行SQL-1]
    B --> C[执行SQL-2]
    C --> D{是否异常?}
    D -- 否 --> E[COMMIT]
    D -- 是 --> F[ROLLBACK]

2. ACID 四大特性

2.1 原子性(Atomicity)

事务内操作不可分割,失败则整体回滚。

2.2 一致性(Consistency)

事务执行前后,数据从一个合法状态变到另一个合法状态。

2.3 隔离性(Isolation)

并发事务之间互不干扰,不会读到“脏中间态”。

2.4 持久性(Durability)

事务提交后,数据永久生效(即使系统异常重启)。


3. 事务基础操作

sql
-- 开启事务
START TRANSACTION;
-- 或 BEGIN;

-- 业务 SQL
UPDATE account SET balance = balance - 100 WHERE id = 1;
UPDATE account SET balance = balance + 100 WHERE id = 2;

-- 成功后提交
COMMIT;

-- 出错时回滚
ROLLBACK;

常用设置:

sql
-- 查看是否自动提交(默认 1)
SELECT @@autocommit;

-- 关闭自动提交(当前会话)
SET autocommit = 0;

4. 并发事务的典型问题

4.1 脏读(Dirty Read)

一个事务读到了另一个未提交事务修改的数据。

4.2 不可重复读(Non-repeatable Read)

同一事务内,两次读取同一行数据,结果不一致(中途被别的事务更新并提交)。

4.3 幻读(Phantom Read)

同一事务内,两次按条件查询,第二次多/少了“新行”(中途被别的事务插入/删除并提交)。

sequenceDiagram
    participant T1 as 事务T1
    participant T2 as 事务T2
    T1->>DB: SELECT count(*) WHERE status=1
    T2->>DB: INSERT 一条 status=1 并 COMMIT
    T1->>DB: 再次 SELECT count(*) WHERE status=1
    Note over T1: 两次结果不同(幻读)

5. 四种隔离级别

MySQL 支持 4 个标准隔离级别(从低到高):

  1. READ UNCOMMITTED
  2. READ COMMITTED
  3. REPEATABLE READ(MySQL InnoDB 默认)
  4. SERIALIZABLE

5.1 对并发问题的影响

隔离级别脏读不可重复读幻读并发性能
READ UNCOMMITTED可能可能可能
READ COMMITTED避免可能可能较高
REPEATABLE READ避免避免InnoDB 下大多可控
SERIALIZABLE避免避免避免

5.2 查看与设置隔离级别

sql
-- 查看当前会话隔离级别
SELECT @@transaction_isolation;

-- 设置当前会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

6. InnoDB 的 MVCC 与锁(入门)

6.1 MVCC(多版本并发控制)

READ COMMITTED / REPEATABLE READ 下,MySQL 通过“数据多版本 + Read View”降低读写冲突。

  • 普通 SELECT:通常是快照读(不加锁)
  • 更新类语句:当前读,需要加锁

6.2 常见锁类型

  • 共享锁(S):读锁,可并发读
  • 排他锁(X):写锁,阻塞其他读写
  • 行锁:锁具体行(InnoDB 常见)
  • 间隙锁/临键锁:用于控制范围并发与幻读(RR 下常见)

7. 事务实战:转账场景

7.1 建表

sql
CREATE TABLE account (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(50) NOT NULL,
  balance DECIMAL(12,2) NOT NULL DEFAULT 0
);

7.2 正确的事务写法

sql
START TRANSACTION;

UPDATE account SET balance = balance - 100
WHERE id = 1 AND balance >= 100;

UPDATE account SET balance = balance + 100
WHERE id = 2;

COMMIT;

业务代码里要判断每一步受影响行数,任一步失败就 ROLLBACK


8. 事务使用建议

  1. 事务尽量短:减少锁持有时间,降低阻塞概率。
  2. 先查后改要谨慎:高并发下优先原子更新(如 balance = balance - ?)。
  3. 按固定顺序加锁:降低死锁概率。
  4. 避免事务内长耗时操作:如外部 HTTP 调用、复杂计算。
  5. 关键 SQL 建索引:避免锁范围扩大导致性能雪崩。

9. 面试高频点

9.1 事务和锁是什么关系

  • 事务保障逻辑原子性与一致性
  • 锁/MVCC 是实现隔离性的手段

9.2 为什么 MySQL 默认是 REPEATABLE READ

  • 在一致性和性能之间做平衡
  • 配合 MVCC 和间隙锁,能较好控制并发异常

9.3 什么是死锁,怎么处理

  • 两个事务互相等待对方释放锁,形成循环等待
  • 处理方式:缩短事务、统一加锁顺序、重试机制、必要时拆分事务

9.4 RC 和 RR 怎么选

  • 对“可重复读一致性”要求高,优先 RR
  • 更关注并发吞吐且可接受一定读一致性差异,可考虑 RC

10. 练习题

  1. 解释 ACID 的四个特性并各举一个业务例子。
  2. 用自己的话说明脏读、不可重复读、幻读的区别。
  3. 写 SQL 查看并设置当前会话事务隔离级别。
  4. 设计一个“扣库存 + 生成订单”的事务流程(伪 SQL 即可)。
  5. 描述你会如何排查并减少死锁。

11. 本章小结

  • 事务是业务正确性的底线,隔离级别是“正确性与性能”的权衡器。
  • 理解并发问题比死记定义更重要,核心在于“何时读、读到什么、谁在改”。
  • 会写 BEGIN/COMMIT 只是入门,真正能力是:并发下依然能保证业务正确。