隔离级别:READ UNCOMMITTED 到 SERIALIZABLE
并发问题
多个事务同时访问数据时,会产生以下问题:
1. 脏读(Dirty Read)
读取到其他事务未提交的数据。
sql
-- 事务 A
BEGIN;
UPDATE accounts SET balance = 200 WHERE id = 1;
-- 未提交
-- 事务 B
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 200(脏数据)
COMMIT;
-- 事务 A
ROLLBACK; -- 回滚,balance 恢复为 100事务 B 读到了事务 A 未提交的数据(200),但 A 最终回滚了。
2. 不可重复读(Non-repeatable Read)
同一事务中两次读取同一数据,结果不同。
sql
-- 事务 A
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 100
-- 事务 B
BEGIN;
UPDATE accounts SET balance = 200 WHERE id = 1;
COMMIT;
-- 事务 A
SELECT balance FROM accounts WHERE id = 1; -- 200(不同了)
COMMIT;3. 幻读(Phantom Read)
同一事务中两次查询,结果集的行数不同。
sql
-- 事务 A
BEGIN;
SELECT COUNT(*) FROM orders WHERE user_id = 1; -- 5 行
-- 事务 B
BEGIN;
INSERT INTO orders (user_id, product, amount) VALUES (1, 'Mouse', 25);
COMMIT;
-- 事务 A
SELECT COUNT(*) FROM orders WHERE user_id = 1; -- 6 行(多了一行)
COMMIT;四种隔离级别
SQL 标准定义了四种隔离级别,从低到高:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 |
| READ COMMITTED | 不可能 | 可能 | 可能 |
| REPEATABLE READ | 不可能 | 不可能 | 可能 |
| SERIALIZABLE | 不可能 | 不可能 | 不可能 |
READ UNCOMMITTED:读未提交
最低隔离级别,允许脏读。
sql
-- PostgreSQL
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- MySQL
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;特点:
- 允许读取未提交的数据
- 性能最高,并发最好
- 数据一致性最差
应用场景:统计报表、日志分析(允许不精确)
READ COMMITTED:读已提交
只能读取已提交的数据,防止脏读。
sql
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 只看到已提交的值
COMMIT;特点:
- 防止脏读
- 允许不可重复读和幻读
- PostgreSQL 默认级别
应用场景:大多数 Web 应用、电商系统
REPEATABLE READ:可重复读
同一事务中多次读取同一数据,结果一致。
sql
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 100
-- 其他事务修改并提交
SELECT balance FROM accounts WHERE id = 1; -- 仍然是 100
COMMIT;特点:
- 防止脏读和不可重复读
- 允许幻读(SQL 标准)
- MySQL InnoDB 默认级别(实际上也防止幻读)
应用场景:金融系统、需要数据一致性的应用
SERIALIZABLE:可串行化
最高隔离级别,完全隔离,如同事务串行执行。
sql
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN;
SELECT * FROM orders WHERE user_id = 1;
-- 其他事务无法插入、更新、删除该用户的订单
COMMIT;特点:
- 防止所有并发问题
- 性能最低,并发最差
- 可能产生死锁
应用场景:关键金融交易、需要严格一致性的场景
设置隔离级别
会话级别
sql
-- PostgreSQL
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- MySQL
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;事务级别
sql
-- PostgreSQL
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- MySQL
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;查看当前隔离级别
sql
-- PostgreSQL
SHOW TRANSACTION ISOLATION LEVEL;
-- MySQL
SELECT @@transaction_isolation;PostgreSQL vs MySQL
PostgreSQL
- 默认:READ COMMITTED
- REPEATABLE READ 使用 MVCC,防止幻读
- SERIALIZABLE 使用 SSI(Serializable Snapshot Isolation)
MySQL InnoDB
- 默认:REPEATABLE READ
- REPEATABLE READ 使用 Next-Key Locks,防止幻读
- READ UNCOMMITTED 很少使用
实际测试
测试脏读
sql
-- 会话 1
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
-- 会话 2
BEGIN;
UPDATE accounts SET balance = 200 WHERE id = 1;
-- 不提交
-- 会话 1
SELECT balance FROM accounts WHERE id = 1; -- 200(脏读)
-- 会话 2
ROLLBACK;
-- 会话 1
SELECT balance FROM accounts WHERE id = 1; -- 100(之前读到的是脏数据)
COMMIT;测试不可重复读
sql
-- 会话 1
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
SELECT balance FROM accounts WHERE id = 1; -- 100
-- 会话 2
BEGIN;
UPDATE accounts SET balance = 200 WHERE id = 1;
COMMIT;
-- 会话 1
SELECT balance FROM accounts WHERE id = 1; -- 200(不可重复读)
COMMIT;测试幻读
sql
-- 会话 1
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN;
SELECT COUNT(*) FROM orders WHERE user_id = 1; -- 5
-- 会话 2
INSERT INTO orders (user_id, product, amount) VALUES (1, 'Mouse', 25);
COMMIT;
-- 会话 1(MySQL)
SELECT COUNT(*) FROM orders WHERE user_id = 1; -- 仍然 5(防止幻读)
-- 会话 1(PostgreSQL)
SELECT COUNT(*) FROM orders WHERE user_id = 1; -- 仍然 5(MVCC)
COMMIT;选择隔离级别
| 场景 | 推荐级别 | 原因 |
|---|---|---|
| Web 应用 | READ COMMITTED | 平衡性能与一致性 |
| 金融系统 | REPEATABLE READ / SERIALIZABLE | 严格一致性 |
| 统计报表 | READ UNCOMMITTED | 允许不精确,提高性能 |
| 秒杀活动 | SERIALIZABLE | 防止超卖 |
| 普通查询 | 默认级别 | 无需特殊处理 |
性能影响
隔离级别越高,性能越低:
text
READ UNCOMMITTED <-- 最快
READ COMMITTED
REPEATABLE READ
SERIALIZABLE <-- 最慢原因:
- 更高级别需要更多锁
- 更多的冲突和等待
- SERIALIZABLE 可能需要重试事务
练习
测试脏读:设置 READ UNCOMMITTED,复现脏读场景。
测试不可重复读:设置 READ COMMITTED,观察不可重复读。
测试幻读:设置 REPEATABLE READ,测试是否防止幻读。
隔离级别对比:同一查询在不同隔离级别下的结果对比。
性能测试:对比不同隔离级别下的并发插入性能。
死锁模拟:在 SERIALIZABLE 下模拟死锁。
查看默认级别:查看数据库的默认隔离级别。
实际场景:设计秒杀活动,选择合适的隔离级别防止超卖。
完成练习后,打开本章答案页核对。
下一步
学会了隔离级别。下一节学习锁机制:理解行锁、表锁、意向锁,掌握死锁的处理。