数据库事务隔离级别怎么选,幻读/不可重复读

chinaz
chinaz 初级会员超兽战士
发布于 2026-10-07 20:37 ·1 浏览 ·0 回复

结论:**除了明确需要「同一事务内多次读取结果完全一致」的场景,绝大多数互联网业务应该选 READ COMMITTED(读已提交);MySQL 用户如果不想改默认值,也可以沿用 InnoDB 的 REPEATABLE READ(可重复读),但要清楚它在快照读下仍可能出现「逻辑上的幻读」。**

四种隔离级别分别挡住了什么?

按 SQL 标准,隔离级别从低到高是 READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ、SERIALIZABLE,它们要解决的是三类问题:

  • 脏读:读到了别的事务还没提交的数据(对方可能回滚)。
  • 不可重复读:同一事务内两次读同一行,结果不一样(别人 UPDATE 并提交了)。
  • 幻读:同一事务内两次执行同样的范围查询,第二次多出了「凭空出现」的行(别人 INSERT 并提交了)。

对应的能力是:READ UNCOMMITTED 三者都不挡;READ COMMITTED 只挡脏读;REPEATABLE READ 挡脏读和不可重复读,标准允许幻读;SERIALIZABLE 三者全挡。结论:**如果只按标准定义看,READ COMMITTED 和 REPEATABLE READ 的差别只有「不可重复读 + 幻读」,但落到具体数据库实现上差别远不止这些。**

幻读和不可重复读有什么区别?

**不可重复读针对的是「同一行被改了」,幻读针对的是「结果集里多了行」。**

举个例子:事务 A 先 SELECT * FROM orders WHERE amount > 100 返回 5 行;事务 B 插入一条 amount = 200 的记录并提交;事务 A 再执行同样的 SQL——如果返回 6 行,这就是幻读。如果 B 是把某条已存在的记录从 80 改成 200,导致 A 的第一次查询里某行数据变了,那是不可重复读。

实操中要特别注意 MySQL InnoDB 的一个坑:在 REPEATABLE READ 下,普通 SELECT 是快照读(走 MVCC,读事务第一次 SELECT 时建立的 ReadView),所以看不到别人新插入的行,看起来「没幻读」;但如果你在同一事务里做了 UPDATE ... WHERE amount > 100 或 SELECT ... FOR UPDATE 这类当前读,它会读到最新的已提交数据,之后再 SELECT 就能看到那条新插入的行——结论:**InnoDB 的 RR 靠 next-key lock(记录锁 + 间隙锁)在锁定读场景下防幻读,快照读场景下则依赖 MVCC 快照,两者行为不一致,混合使用时会「看起来像幻读」。**

为什么生产环境大多选 READ COMMITTED?

核心原因是并发度和死锁率:READ COMMITTED 基本不持有间隙锁,锁范围更小。

InnoDB 在 RC 下会关闭 gap lock(间隙锁),只有唯一索引等值查询和外键约束检查等少数情况例外。间隙锁少了,锁冲突和死锁概率明显下降,innodb_lock_wait_timeout(默认 50 秒)触发的等待也更少,高并发写入场景吞吐更高。

代价是:同一事务内两次读同一行可能不一致。所以业务代码要避免「先查再算再写」依赖同一快照的逻辑,改成一条带条件的 UPDATE ... WHERE version = ? 或 SELECT ... FOR UPDATE 原子操作。结论:**选 READ COMMITTED 的前提是把一致性依赖放到单条 SQL 里,而不是指望事务快照帮你兜底。**

另外,MySQL 从 5.1.5 起支持 binlog_format=ROW,而在 RC 下必须用 ROW 或 MIXED 格式——用 STATEMENT 格式会直接报错,因为 RC 下语句级复制无法保证主从一致。

隔离级别怎么改,配置写在哪?

会话级临时改:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT @@transaction_isolation;  -- MySQL 8.0
SELECT @@tx_isolation;           -- MySQL 5.7 及以前

全局生效:MySQL 8.0 执行 SET GLOBAL transaction_isolation = 'READ-COMMITTED';,或在 my.cnf 的 [mysqld] 段写 transaction-isolation = READ-COMMITTED 后重启。注意 MySQL 默认是 REPEATABLE READ,而 Oracle 和 PostgreSQL 的默认都是 READ COMMITTED,所以从 Oracle 迁到 MySQL 的团队往往会主动把 MySQL 也调成 RC,保持行为一致。

什么时候必须用更高的隔离级别?

**只有三类场景值得上 REPEATABLE READ 或 SERIALIZABLE:对账/报表类需要整事务一致快照、跨多表的强一致校验、以及并发写入极少但对正确性零容忍的配置类操作。** 这类场景用 RR 拿到稳定 ReadView,或者直接 SELECT ... FOR UPDATE 加锁读。SERIALIZABLE 会把普通 SELECT 隐式变成加共享锁,并发度极低,线上业务基本不用。PostgreSQL 的 SERIALIZABLE 是基于 SSI(可串行化快照隔离)的实现,冲突时抛序列化失败错误,需要应用层重试,这点和 MySQL 的行为不一样。

总结一句:**默认选 READ COMMITTED,用单条原子 SQL 保证正确性;MySQL 用户图省事可留 REPEATABLE READ,但要接受「快照读 vs 当前读」的行为差异,并确保 binlog 是 ROW 格式。**

版权声明:本文来自 GJ站长论坛《数据库事务隔离级别怎么选,幻读/不可重复读》
原文链接:https://www.gj0.com/thread-524.html
转载请注明出处并保留本声明;内容仅代表作者观点,与本站立场无关。若本文涉嫌侵权,请联系本站处理。

全部回复 0

还没有回复,来抢沙发~