【数据库】tdsql(MySQL )的事务隔离级别
MySQL 的事务隔离级别是数据库并发控制的核心概念,用于解决多个事务同时执行时可能产生的数据不一致问题。
1. 默认值是
MySQL InnoDB 默认的事务隔离级别是:REPEATABLE READ(可重复读)。
2. 四大隔离级别
三个读异常(脏话、不可重复、幻读)解释:
- 脏读(Dirty Read):读到别的事务尚未提交的数据(万一对方回滚,这数据就是废的)。
- 不可重复读(Non-Repeatable Read):在同一事务内,两次读取同一条记录,因为别的事务修改并提交了,导致两次结果不一致(侧重于“改”)。
- 幻读(Phantom Read):在同一事务内,两次执行同一个范围查询,因为别的事务插入或删除了数据,导致第二次多出或少了几行(侧重于“增删”)。
隔离级别与异常的关系
注意:按照 SQL 标准,
REPEATABLE READ是允许幻读发生的。但 MySQL InnoDB 通过强大的 Next-Key Lock(间隙锁+行锁)机制,在这个级别下就杜绝了幻读。
3.MySQL 默认选择 REPEATABLE READ
绝大多数数据库(如 Oracle、PostgreSQL、SQL Server)默认都是READ COMMITTED, MySQL 偏要与众不同。
背后的原因不是“拍脑袋”,而是由MySQL 主从复制(基于 Binlog)的历史架构和性能权衡共同决定的。我们可以把原因归结为三大支柱。
一:历史包袱与二进制日志(Binlog)的安全隐患(最核心原因)
在 MySQL 5.0 及更早版本,以及当下的某些配置中,Binlog 的格式默认是STATEMENT(基于语句的复制)。也就是记录UPDATE t SET a=1 WHERE id>10;这样的 SQL 语句,然后发给从库执行。
如果此时隔离级别是READ COMMITTED,会产生一个致命的主从数据不一致风险,我们称之为“Binlog 惊魂”。
让我们用一个经典场景复现:
假设主库并发执行以下两个事务(TxA 删除,TxB 插入):
- 时间线 1:TxA 执行
DELETE FROM users WHERE age > 20;(此时表中有 id=1(age=25) 和 id=2(age=30),TxA 扫到了这两行,准备删除)。 - 时间线 2:TxB 执行
INSERT INTO users (id=3, age=28);并提交。 - 时间线 3:在
READ COMMITTED下,由于 TxB 已提交,TxA 重新评估条件age>20,新插入的 id=3 不会被删除(因为 InnoDB 的删除是通过隐藏字段标记,此时读视图变化了)。 - 时间线 4:TxA 提交。
主库结果:id=1, id=2 被删,id=3 存活。
Binlog 记录顺序(STATEMENT 格式):INSERT(TxB)的记录先写,DELETE(TxA)的记录后写。
从库重放:先执行INSERT(插入 id=3),后执行DELETE FROM users WHERE age>20;。在从库执行 DELETE 时,它会无情地把刚刚插入的 id=3 也一起删掉!
- 从库结果:id=1, id=2, id=3 全部被删。
结果:主从数据严重不一致!
如何解决?MySQL 选择了REPEATABLE READ。在这个级别下,TxA 在开始时创建了一致性读视图(快照),直到事务结束。即使 TxB 提交了,TxA 依然看不到新插入的 id=3,所以 DELETE 语句只作用于它一开始看到的 id=1 和 id=2。这条 DELETE 记录到 Binlog 后,在从库执行时,也会基于从库的 MVCC 快照(或锁机制)只删除这两条,从而保证主从一致。
二:MVCC 实现的读视图(Read View)生成时机不同
为了理解这一点,我们需要看一眼 InnoDB 的多版本并发控制(MVCC)机制。
- 在
READ COMMITTED下:每一次执行普通SELECT语句,都会重新生成一个新的 Read View(读视图)。 - 在
REPEATABLE READ下:事务内第一次执行SELECT时生成一个 Read View,并复用到事务结束。
这种机制天然保证了“可重复读”,并且为基于 STATEMENT 的复制提供了确定性。虽然生成 Read View 的代价很小,但REPEATABLE READ减少了生成的次数,在极高并发下对 CPU 也算一种微小的优化。
三:间隙锁(Gap Lock)带来的“伪串行化”优势
虽然REPEATABLE READ带来了更高的数据一致性保障,但它也有代价——间隙锁。为了阻止幻读,InnoDB 不仅锁住命中的行(Record Lock),还会锁住索引记录之间的“间隙”(Gap Lock)。
MySQL 选择
REPEATABLE READ,核心是为了兼容早期的 STATEMENT 格式 Binlog 以保证主从一致性。虽然现在有了更安全的ROW(基于行)格式 Binlog(推荐使用),但这个默认级别作为历史传承和“更安全”的默认配置,被保留了下来。
4. 现代视角下的权衡
既然 ROW 格式 Binlog 解决了主从不一致问题,为什么 MySQL 8.0 依然不改默认值?
- 兼容性与平滑升级:对于无数遗留系统,修改默认隔离级别可能引发未知的性能回退或死锁变化,作为基础软件,MySQL 必须保持高度的向下兼容。
- 业务语义更严谨:对于金融、报表统计等需要事务内多次读取同一批数据的场景,
REPEATABLE READ直接提供了开箱即用的强一致性,减少了应用层处理“不可重复读”的代码负担。
但也要注意它的“副作用”:
由于间隙锁的存在,在高并发写入(尤其是高冲突的插入场景)下,REPEATABLE READ比READ COMMITTED更容易产生死锁。因为间隙锁是“范围锁”,两个事务很容易相互等待。
5. MySQL 的隔离级别决策逻辑
结论
“MySQL 默认是 REPEATABLE READ。这主要源于早期的 STATEMENT 模式 Binlog 复制,如果使用 READ COMMITTED 会导致主从数据不一致。虽然现在我们可以通过设置 binlog_format=ROW 并切换到 READ COMMITTED 来获得更高并发和更少死锁,但 InnoDB 为了向后兼容和提供更稳健的默认行为,在 8.0 版本中依然保留了 REPEATABLE READ 作为默认值。”
