MySQL事务隔离级别与一致性机制
事务与一致性基础
在现代数据库系统中,事务(Transaction)是保证数据可靠性与一致性的核心机制。事务是数据库管理系统提供的一种逻辑执行单元,它由一组操作组成,这些操作要么全部成功执行,要么全部失败回滚,从而保证系统状态的一致性。
事务最早由数据库理论中的 ACID 特性定义:
- Atomicity(原子性)
- Consistency(一致性)
- Isolation(隔离性)
- Durability(持久性)
其中,一致性与隔离性之间存在密切关系。数据库通过 事务隔离级别(Isolation Level) 来平衡并发性能与数据一致性。
在高并发系统中,如果多个事务同时访问同一数据,可能产生各种并发问题,例如:
- 脏读(Dirty Read)
- 不可重复读(Non-repeatable Read)
- 幻读(Phantom Read)
为了解决这些问题,数据库引入了 事务隔离级别。
ACID 中的一致性含义
一致性(Consistency)指的是:
事务执行前后,数据库必须从一个一致状态转移到另一个一致状态。
所谓一致状态,是指数据库满足所有约束条件,例如:
- 主键约束
- 外键约束
- 唯一约束
- 业务逻辑约束
例如银行转账:
A 账户:1000
B 账户:1000
A 向 B 转账 200。
事务执行:
A = A - 200
B = B + 200
事务执行前:
总金额 = 2000
事务执行后:
总金额 = 2000
如果事务执行到一半系统崩溃:
A = 800
B = 1000
总金额变为:
1800
此时数据库处于 不一致状态。
因此数据库必须保证:
- 要么全部执行
- 要么全部回滚
这就是 原子性保证一致性。
但在并发环境中,仅靠原子性无法解决问题,还需要 隔离性。
并发事务产生的问题
为了理解隔离级别,必须先理解并发事务可能出现的问题。
假设存在如下表:
CREATE TABLE account (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50),
balance DECIMAL(10,2)
) ENGINE=InnoDB;
初始化数据:
INSERT INTO account(name,balance) VALUES
('Alice',1000),
('Bob',1000);
脏读(Dirty Read)
脏读是指:
一个事务读取了另一个事务 尚未提交的数据
示例:
事务 A:
START TRANSACTION;
UPDATE account
SET balance = 500
WHERE name='Alice';
事务 B:
SELECT balance FROM account WHERE name='Alice';
事务 B 读取到:
500
如果事务 A 回滚:
ROLLBACK;
数据库实际数据仍然是:
1000
事务 B 读取的数据 从未存在过。
这种现象称为 脏读。
不可重复读(Non-repeatable Read)
不可重复读是指:
同一个事务内多次读取同一行数据,结果不同。
事务 A:
START TRANSACTION;
SELECT balance FROM account WHERE name='Alice';
返回:
1000
事务 B:
UPDATE account
SET balance=800
WHERE name='Alice';
COMMIT;
事务 A 再次读取:
SELECT balance FROM account WHERE name='Alice';
返回:
800
同一个事务内读取同一行,结果不同。
这就是 不可重复读。
幻读(Phantom Read)
幻读是指:
同一事务中两次执行范围查询,返回的记录数量不同。
假设存在订单表:
CREATE TABLE orders (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
user_id BIGINT,
amount DECIMAL(10,2)
) ENGINE=InnoDB;
事务 A:
START TRANSACTION;
SELECT * FROM orders WHERE amount > 100;
返回:
2 条记录
事务 B:
INSERT INTO orders(user_id,amount)
VALUES (1,200);
COMMIT;
事务 A 再次查询:
SELECT * FROM orders WHERE amount > 100;
返回:
3 条记录
突然多出一条数据,就像出现“幻影”。
这就是 幻读。
SQL 标准事务隔离级别
SQL-92 标准定义了四种事务隔离级别。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
隔离级别越高:
- 数据一致性越强
- 并发性能越低
数据库系统通常在 一致性与性能之间做权衡。
MySQL 事务隔离级别
MySQL InnoDB 默认隔离级别是:
REPEATABLE READ
查询当前隔离级别:
SELECT @@transaction_isolation;
设置隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
或者:
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;
READ UNCOMMITTED
READ UNCOMMITTED 是最低隔离级别。
特点:
- 允许读取未提交数据
- 可能发生脏读
事务执行过程中不进行严格隔离。
优点:
- 并发性能最好
缺点:
- 数据一致性最差
这种级别在实际生产环境中几乎不会使用。
READ COMMITTED
READ COMMITTED 是多数数据库默认隔离级别。
例如:
- Oracle
- PostgreSQL
- SQL Server
特点:
- 只能读取已提交数据
- 避免脏读
- 可能发生不可重复读
执行机制:
每次读取数据时,都会读取 最新已提交版本。
示例:
事务 A:
START TRANSACTION;
SELECT balance FROM account WHERE id=1;
事务 B 更新并提交:
UPDATE account
SET balance=800
WHERE id=1;
COMMIT;
事务 A 再次读取:
SELECT balance FROM account WHERE id=1;
结果不同。
REPEATABLE READ
REPEATABLE READ 的目标是:
保证事务内多次读取同一行数据结果一致。
MySQL InnoDB 默认使用该隔离级别。
特点:
- 不会发生脏读
- 不会发生不可重复读
- 理论上可能幻读
但是 MySQL InnoDB 实际上通过 Next-Key Lock 解决了幻读问题。
MVCC 多版本并发控制
MySQL InnoDB 实现高并发的重要机制是:
MVCC(Multi-Version Concurrency Control)
MVCC 的核心思想:
同一数据行维护多个版本,通过版本号控制可见性。
每一行数据包含隐藏字段:
DB_TRX_ID(事务 ID)DB_ROLL_PTR(回滚指针)
通过 undo log 维护历史版本。
示意:
row1
├─ version1
├─ version2
└─ version3
读取时:
- 根据事务 ID 判断可见版本
- 不需要加锁
优点:
- 提高并发能力
- 减少锁竞争
Read View 机制
MVCC 通过 Read View 决定数据可见性。
Read View 包含:
- 当前活跃事务列表
- 最小事务 ID
- 最大事务 ID
判断规则:
如果数据版本:
- 小于最小事务 ID → 可见
- 大于最大事务 ID → 不可见
- 在活跃事务列表 → 不可见
通过这种方式保证:
- 事务读取一致快照
当前读与快照读
MySQL 将读取分为两种类型:
快照读
普通查询:
SELECT * FROM account;
使用 MVCC。
特点:
- 不加锁
- 读取历史版本
当前读
需要获取最新数据并加锁:
SELECT ... FOR UPDATE
SELECT ... LOCK IN SHARE MODE
UPDATE
DELETE
INSERT
当前读会加锁,保证数据一致性。
InnoDB 锁机制
InnoDB 使用多种锁保证事务隔离。
主要包括:
- 行锁(Row Lock)
- 间隙锁(Gap Lock)
- 临键锁(Next-Key Lock)
行锁
锁定某一行记录:
UPDATE account
SET balance=100
WHERE id=1;
只锁定 id=1 的记录。
Gap Lock
锁定索引区间。
例如:
SELECT * FROM account
WHERE id BETWEEN 10 AND 20
FOR UPDATE;
锁定:
(10,20)
防止插入新数据。
Next-Key Lock
Next-Key Lock 是:
行锁 + Gap Lock
作用:
- 防止幻读
例如:
SELECT * FROM account
WHERE id=10
FOR UPDATE;
锁定:
(9,10]
SERIALIZABLE
SERIALIZABLE 是最高隔离级别。
特点:
- 所有事务串行执行
- 不会出现任何并发问题
数据库实现方式:
- 对查询加共享锁
- 对写操作加排他锁
优点:
- 完全一致性
缺点:
- 并发能力极低
- 性能开销巨大
因此在实际生产系统中极少使用。
不同数据库默认隔离级别
| 数据库 | 默认隔离级别 |
|---|---|
| MySQL InnoDB | REPEATABLE READ |
| Oracle | READ COMMITTED |
| PostgreSQL | READ COMMITTED |
| SQL Server | READ COMMITTED |
MySQL 选择更高隔离级别的原因:
- 结合 MVCC
- 通过 Next-Key Lock 解决幻读
因此性能损失较小。
事务隔离级别的实际选择
在实际系统设计中,需要根据业务特点选择隔离级别。
| 场景 | 推荐隔离级别 |
|---|---|
| 高并发读系统 | READ COMMITTED |
| 金融系统 | REPEATABLE READ |
| 强一致系统 | SERIALIZABLE |
例如:
电商系统:
- 订单查询 → READ COMMITTED
- 库存扣减 → REPEATABLE READ
通过合理设计:
- 保证数据一致性
- 提高系统吞吐量
Spring 事务隔离级别
在 Spring 中,可以通过 @Transactional 指定隔离级别。
示例:
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transfer() {
// 业务逻辑
}
Spring 支持以下隔离级别:
Isolation.DEFAULT
Isolation.READ_UNCOMMITTED
Isolation.READ_COMMITTED
Isolation.REPEATABLE_READ
Isolation.SERIALIZABLE
DEFAULT 表示使用数据库默认隔离级别。
分布式系统中的一致性
在微服务架构中,单数据库事务无法覆盖多个服务。
此时需要使用:
- 分布式事务
- 最终一致性
常见解决方案:
2PC
两阶段提交。
问题:
- 阻塞
- 性能差
TCC
Try / Confirm / Cancel
特点:
- 业务层控制事务
Saga
通过补偿事务实现一致性。
流程:
事务A → 事务B → 事务C
失败时反向补偿
总结
事务隔离级别是数据库并发控制的核心机制,通过不同隔离级别可以在 一致性与性能之间取得平衡。
SQL 标准定义了四种隔离级别:
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
MySQL InnoDB 默认使用 REPEATABLE READ,并结合 MVCC 与 Next-Key Lock 实现高并发与强一致性。
在实际系统设计中,需要根据业务需求选择合适隔离级别,同时在分布式系统中结合最终一致性策略,才能构建既高性能又可靠的系统架构。