理解MySQL事务隔离
在实际开发中,MySQL 服务往往被多个客户端同时访问,这些访问通常以事务的形式进行。一个事务可能包含多条 SQL,原子性要求让用户要么看到执行前的状态,要么看到执行后的状态。当多个事务并发执行时,它们可能会互相影响,比如同时操作同一张表甚至同一行数据。为了让事务执行过程中尽量不受干扰,数据库引入了隔离性。而允许事务受到不同程度的干扰,就对应了不同的隔离级别。
这就好比试衣服:进试衣间需要完全隔离,而试鞋子可能就不需要那么严格的隔离。

四种隔离级别
标准定义了四种隔离级别,按隔离程度由低到高:
- 读未提交(Read Uncommitted):所有事务都能看到其他事务未提交的修改。这相当于几乎没有隔离,生产环境基本不用。
- 读提交(Read Committed):一个事务提交后,另一个事务才能看到它的修改。但这会导致同一个事务内多次读取同一数据可能得到不同的值,即不可重复读。
- 可重复读(Repeatable Read):MySQL 的默认级别。事务执行过程中看到的数据与事务开始时一致,其他事务的提交对当前事务不可见,只有都结束并重新启动后才能看到新结果。
- 串行化(Serializable):最高的隔离级别,事务一个接一个串行执行,效率最低但最安全。
隔离大多通过锁机制实现,不同级别使用的锁策略也不同。注意这几种级别主要针对读写并发,读读场景通常还是串行化处理。
如何查看和设置隔离级别
隔离级别可以在会话(Session)或全局(Global)级别设置。会话级只影响当前连接,全局级影响所有新连接(已存在的连接需重新登录才生效)。
查看当前隔离级别:
-- 查看会话级
SELECT @@session.tx_isolation;
-- 查看全局级
SELECT @@global.tx_isolation;
-- 简写,效果同上
SELECT @@tx_isolation;
设置语法:
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL
{READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE};
例如,把当前会话设为串行化:
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 验证
SELECT @@global.tx_isolation; -- 可能仍是 REPEATABLE-READ
SELECT @@session.tx_isolation; -- 应输出 SERIALIZABLE
设置全局为读未提交:
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT @@global.tx_isolation; -- 输出 READ-UNCOMMITTED
如果没有立即生效,一般关闭客户端重连一下就好。
脏读与不可重复读实例
读未提交下的脏读
在读未提交级别,一个事务能读到另一个事务还未提交的更新。不加锁保护,效率高但数据一致性极差,强烈不建议使用。
打开两个终端,先确保隔离级别设为此级别。
-- 终端 A
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 重连后验证
SELECT @@tx_isolation;
然后终端 A 开启事务但暂不提交:
BEGIN;
UPDATE account SET blance = 123.0 WHERE id = 1;
-- 没有 COMMIT
终端 B 在另一个事务里查询:
BEGIN;
SELECT * FROM account;
-- 此时会读到终端 A 未提交的 123.00,这就是脏读
读提交下的不可重复读
切换到读提交级别:
-- 终端 A
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 重连验证
终端 A 开启事务更新数据:
BEGIN;
UPDATE account SET blance = 321.0 WHERE id = 1;
-- 尚未提交
终端 B 第一次查询:
BEGIN;
SELECT * FROM account;
-- 此时读到旧值
终端 A 提交:
COMMIT;
终端 B 再次查询:
SELECT * FROM account;
-- 这次读到了 321.0,同一个事务里两次读取结果不同,即不可重复读。
这个不一致会带来实际问题。 比如小王负责把 Tom 的工资从 3200 改到 4500,小张负责根据工资发奖品。小张第一次查询看到 3200(符合发奖条件),但还没处理完,小王提交了更新,小张第二次查询看到 4500(不符合条件),导致业务判断出错。这种同一事务内的数据波动,往往是业务逻辑的 bug 源头。
因此,虽然读提交比读未提交看起来'高级'一些,但不可重复读的问题让它很少直接用于生产,除非业务明确能容忍这类不一致。大多数情况下,我们还是会用可重复读甚至串行化来保障数据稳定性。
选择隔离级别需要在并发性能和数据一致性之间做出权衡,没有绝对完美的方案。

