在数据库事务管理中,脏读(Dirty Read)、不可重复读(Non-Repeatable Read)和幻读(Phantom Read) 是三种常见的数据一致性问题,通常与 事务隔离级别 相关。
1️⃣ 脏读(Dirty Read)
📌 定义
一个事务读取了另一个未提交事务修改的数据,如果该事务回滚,读取到的数据就会变成无效数据,导致数据不一致。
📌 示例
假设有一个 users 表:
| id | name | balance |
|---|---|---|
| 1 | Alice | 1000 |
🔹 事务 A(未提交)
BEGIN;
UPDATE users SET balance = 500 WHERE id = 1; -- Alice 余额变为 500
-- 事务 A 还未提交
🔹 事务 B(脏读)
SELECT balance FROM users WHERE id = 1;
-- 事务 B 读取到 balance = 500
🔹 事务 A 回滚
ROLLBACK;
-- 事务 A 撤销修改,balance 恢复到 1000
💥 结果
- 事务 B 读取到了
balance = 500,但实际上这个值被事务 A 回滚 了,导致读到了无效数据。
✅ 解决方案
设置隔离级别 Read Committed(读已提交)或更高,避免读取未提交数据。
2️⃣ 不可重复读(Non-Repeatable Read)
📌 定义
在同一个事务中,两次查询同一行数据,发现第二次查询的数据已经被其他事务修改或删除,导致结果不一致。
📌 示例
假设 users 表:
| id | name | balance |
|---|---|---|
| 1 | Alice | 1000 |
🔹 事务 A
BEGIN;
SELECT balance FROM users WHERE id = 1;
-- 事务 A 读取 balance = 1000
🔹 事务 B(修改数据并提交)
BEGIN;
UPDATE users SET balance = 2000 WHERE id = 1;
COMMIT;
-- 事务 B 修改并提交,Alice 的 balance 变为 2000
🔹 事务 A(再次读取)
SELECT balance FROM users WHERE id = 1;
-- 事务 A 读取到 balance = 2000
💥 结果
- 同一个事务 A,前后两次读取到的数据不一致(1000 → 2000),发生不可重复读。
✅ 解决方案
设置 Repeatable Read(可重复读)隔离级别,事务 A 只能看到事务开始时的数据快照,避免数据被修改。
3️⃣ 幻读(Phantom Read)
📌 定义
在同一个事务中,两次查询同一范围的数据,第二次查询时发现数据“凭空”增加或删除了,但事务没有修改数据。
📌 示例
假设 users 表:
| id | name | balance |
|---|---|---|
| 1 | Alice | 1000 |
| 2 | Bob | 1500 |
🔹 事务 A
BEGIN;
SELECT COUNT(*) FROM users;
-- 事务 A 读取到 2 条数据
🔹 事务 B(插入新数据并提交)
BEGIN;
INSERT INTO users (id, name, balance) VALUES (3, 'Charlie', 1200);
COMMIT;
-- 事务 B 插入新数据
🔹 事务 A(再次查询)
SELECT COUNT(*) FROM users;
-- 事务 A 读取到 3 条数据
💥 结果
- 事务 A 在两次查询之间,发现“凭空”多了一条数据(Charlie),发生幻读。
✅ 解决方案
使用 Serializable(可串行化)隔离级别,强制所有事务串行执行,避免幻读。
📌 对比总结
| 问题 | 定义 | 示例 | 解决方案 |
|---|---|---|---|
| 脏读(Dirty Read) | 读取了未提交事务的数据 | 事务 B 读取到事务 A 未提交的数据,事务 A 回滚后数据失效 | Read Committed 及以上 |
| 不可重复读(Non-Repeatable Read) | 同一事务多次读取,数据被修改 | 事务 A 两次查询,第一次 balance = 1000,第二次 balance = 2000 | Repeatable Read 及以上 |
| 幻读(Phantom Read) | 同一事务多次查询,数据行数变化 | 事务 A 查询 COUNT(*),事务 B 插入新数据,事务 A 发现数据变多 | Serializable 级别 |
📌 事务隔离级别如何防止这些问题?
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| Read Uncommitted(读未提交) | ❌ 可能发生 | ❌ 可能发生 | ❌ 可能发生 |
| Read Committed(读已提交) | ✅ 避免脏读 | ❌ 可能发生 | ❌ 可能发生 |
| Repeatable Read(可重复读,MySQL 默认) | ✅ 避免脏读 | ✅ 避免不可重复读 | ❌ 可能发生 |
| Serializable(可串行化) | ✅ 避免脏读 | ✅ 避免不可重复读 | ✅ 避免幻读 |
✅ 总结
- 脏读:读取了未提交的数据,可以用
Read Committed解决。 - 不可重复读:同一事务多次查询,数据被修改,可以用
Repeatable Read解决(MySQL 默认)。 - 幻读:同一事务多次查询,数据数量发生变化,需要
Serializable解决。
MySQL 默认使用 Repeatable Read,防止了脏读和不可重复读,但仍可能出现幻读。如果要彻底避免幻读,可以使用 Serializable,但会导致性能下降,一般很少使用。

5万+

被折叠的 条评论
为什么被折叠?



