事务隔离级别决定了并发事务能看到对方多少中间状态,级别越高数据越一致,但并发性能越低,这是所有数据库工程师在做读写设计时绕不开的核心权衡点。
事务隔离级别有哪些?从低到高逐个说清楚
SQL标准定义了四种隔离级别,每个级别解决了一部分并发问题,同时也留下了相应的副作用,理解它们最好的方式,是把自己想象成两个同时操作同一行数据的会话。
Read Uncommitted:能看到别人没提交的数据
这个级别最“宽松”,事务A改了数据但还没提交,事务B就能读到这个改动,一旦事务A回滚,事务B读到的就是脏数据,也就是常说的脏读,实际业务中极少使用这个级别,因为没有任何隔离可言,只适合做类似“大概看一眼当前活跃事务改了什么”的调试场景。
Read Committed:提交了才看得见
在这个级别下,事务只能读到其他事务已提交的数据,脏读被解决,但带来了新的问题:同一个事务里,两次执行同样的SELECT,可能会得到不同的结果,比如事务B先读了一行金额为100,随后事务A提交了把这行改成200,事务B再读就变成200,这就是不可重复读,Oralce和PostgreSQL的默认隔离级别就是它。
Repeatable Read:一个事务内读到的结果永远一样
这个级别保证了在事务开启后,第一次读到的结果在整个事务期间保持不变,即使其他事务修改并提交了数据,当前事务也看不到,不可重复读被解决,但幻读仍然有可能会出现,幻读是指事务B按条件查出了5行数据,事务A插入了一行新数据并提交,事务B再查变成了6行,像是出现了幻觉,MySQL的InnoDB引擎通过间隙锁,在多数场景下规避了幻读。
Serializable:完全串行化
最高隔离级别,事务之间严格排队执行,相当于把所有并发操作变成串行,脏读、不可重复读、幻读全部消失,代价是吞吐量断崖式下跌,只有在极少数对一致性要求苛刻、并发量又不高的系统中才会考虑。
mysql默认隔离级别是什么?为什么是它
行业共识认为,MySQL默认使用Repeatable Read,这和大部分关系型数据库不同,原因很务实:MySQL的InnoDB引擎在早期做主从复制时,如果使用READ COMMITTED,binlog的日志格式很难准确记录基于语句的复制,而REPEATABLE READ配合间隙锁,可以让基于语句的复制更稳定。

很多开发者在初学时会困惑“为什么MySQL要用可重复读”,其实就是历史包袱加工程取舍。
查看当前隔离级别的两种命令
登录MySQL客户端,执行:
SELECT @@transaction_isolation;
或者用老版本也兼容的方式:
SHOW VARIABLES LIKE 'transaction_isolation';
执行后你会看到类似REPEATABLE-READ的输出,这个值就是当前会话的隔离级别。
修改隔离级别的操作路径
临时修改当前会话:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
修改整个全局配置,需要SUPER权限:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
修改后只影响新开的事务,已存在的事务不受影响,配置文件里的永久修改,通常在my.cnf的[mysqld]段落添加:
transaction-isolation = READ-COMMITTED
改完需要重启MySQL才能生效,注意,READ COMMITTED和REPEATABLE READ之间的切换,是很多公司做架构优化时经常碰到的动作。
脏读、不可重复读、幻读到底有什么区别?
这三类问题代表了对数据一致性的破坏程度层层递进,下面用一张表格把它们的定义和对应解决关系说清楚。
| 问题 | 发生场景 | 解决它的隔离级别 | 表现特征 |
|---|---|---|---|
| 脏读 | 读到未提交的临时数据 | READ COMMITTED | 数据最终被回滚,读到的是空中楼阁 |
| 不可重复读 | 同一事务内,同一行数据两次读取结果不同 | REPEATABLE READ | 行数据被其他事务更新并提交 |
| 幻读 | 同一事务内,同一个查询条件两次返回行数不同 |
SERIALIZABLE |
有新的行被插入,导致结果集变化 |
举一个实际的电商例子,用户下单后查询库存,如果隔离级别是READ UNCOMMITTED,另一个事务把库存从10改成9但还没提交,你这边就显示9,结果对方事务回滚,库存其实还是10,这就是脏读,会直接导致超卖判断错误,如果把级别提到READ COMMITTED,就不会有这个问题,但可能在同一事务里,第一次查库存是10,第二次因为别的事务提交了改库存的请求而变成9,导致你对“这次查询是否准确”产生怀疑。
REPEATABLE READ下,你从事务开始到最后,看到的库存始终是10,即使其他事务已经改成9,但如果另一个事务插入了一条新的库存记录,比如加了个新仓库,在REPEATABLE READ下你依然查不到,这不算幻读,因为InnoDB的MVCC快照已经固定了结果集,真正的幻读更多出现在把多条记录做聚合的场景中,比如统计全部订单数。
业务系统怎么选事务隔离级别?不同场景下的取舍
没有完美隔离级别,只有适合当前业务的方案,选择时主要看两个维度:一致性要求有多高,并发压力有多大。
金融支付场景:优先选READ COMMITTED
涉及账户余额、交易流水这类强一致性的场景,大多数团队会选READ COMMITTED,它不会读到未提交的数据,也不像REPEATABLE READ那样需要额外的间隙锁,减少了锁竞争,对于转账操作,处理好行级锁就可以避免并发覆盖,不依赖更高的隔离级别,这个选择对系统吞吐量的提升明显,也是很多支付公司从默认的REPEATABLE READ迁到READ COMMITTED的原因。
高并发互联网场景:REPEATABLE READ加合理索引
国内大部分中小团队在初期直接沿用MySQL默认级别,也就是REPEATABLE READ,此时并发读写都依赖InnoDB的间隙锁机制,如果业务表中存在高频插入操作,比如日志表、订单流水表,间隙锁可能导致比较多的锁等待,一种常见优化是,把隔离级别调整为READ COMMITTED,并配合精简的binlog格式(如ROW模式)来保障主从一致,另一种做法是保持默认级别,但通过索引设计缩小锁范围,减少插入间的互相干扰。
地域因素的考量:云数据库默认可选

如果你使用的是简米云、酷番云等云数据库,控制台上一般都能直接切换隔离级别,例如在云数据库MySQL的参数设置里搜索transaction_isolation,选择READ-COMMITTED后提交即可,有些场景里,比如跨地域的多活或灾备,读写分离架构下,从库通常会设置成READ COMMITTED来减少主从延迟时的可见性偏差,这属于架构层面的取舍,需要结合具体业务流量做压测。
事务隔离级别常见问题解答
为什么很多人说MySQL的REPEATABLE READ不会发生幻读?
因为InnoDB在REPEATABLE READ下使用了MVCC多版本并发控制,普通SELECT走快照读,看到的是事务开始时的数据版本,要想发生幻读,需要使用当前读,即SELECT ... FOR UPDATE或UPDATE这类加锁语句,InnoDB通过间隙锁锁住扫描范围,阻止其他事务插入新行,所以常规操作下幻读很难出现,这算是InnoDB对标准隔离级别的一种加强。
隔离级别越低,性能就一定越好吗?
不一定,低隔离级别减少了锁的范围,但可能带来更多的重试逻辑,比如READ UNCOMMITTED几乎不锁,但查询结果不稳定,应用层要处理脏数据,反而增加代码复杂度,READ COMMITTED和REPEATABLE READ在常规点查或索引查询上的性能差距并不大,只有在大量范围扫描和插入并存的场景中,锁竞争差异才会明显体现,做性能对比时,应该用真实的业务SQL去压测,而不是只看隔离级别。
有没有办法在不改隔离级别的情况下解决不可重复读?
可以,利用持久化锁或乐观锁机制,比如在更新时带上版本号:UPDATE account SET balance = 200, version = version + 1 WHERE id = 1 AND version = 5,如果影响行数为0,说明数据已被其他事务修改,需要重新读取,这种方式无论隔离级别是什么,都能保证关键数据的更新不被覆盖,很多高并发秒杀系统就是用这种办法,既保证了数据一致性,又避免了高隔离级别带来的锁开销。
隔离级别的选择,本质上是在数据一致性、系统并发量和开发复杂度之间找平衡点,没有一步到位的配置,只有经过压测和业务验证后,才能确定最适合自己系统的那个级别。
