mysql默认repeatable read下不可重复读本不该发生,实因未显式开启事务或自动提交导致每次查询为独立快照;真正不可重复读发生在read committed级别下同一事务两次查询间被其他事务修改并提交。

不可重复读到底是什么现象
在 MySQL 默认的 REPEATABLE READ 隔离级别下,「不可重复读」本不该发生——但很多人在测试时却看到了:同一事务中两次 SELECT 同一条记录,第二次读到了其他事务已 COMMIT 的修改值。这通常不是隔离级别失效,而是因为用了 SELECT ... FOR UPDATE 或 SELECT ... LOCK IN SHARE MODE 以外的普通查询,且没开启事务(或事务已自动提交),导致每次 SELECT 都成了独立快照。
真正触发不可重复读的典型场景是:事务 A 在 READ COMMITTED 级别下执行两次相同 SELECT,中间事务 B 修改并提交了某行数据——A 第二次查就会看到新值。
怎么确认当前会话的隔离级别
直接查变量比猜更可靠:
SELECT @@tx_isolation; -- 或 SELECT @@transaction_isolation;
注意:@@tx_isolation 在 MySQL 8.0+ 已弃用,优先用 @@transaction_isolation;返回值类似 REPEATABLE-READ(全大写短横线)或 READ-COMMITTED。
- 全局默认值存在
my.cnf的[mysqld]段里,配置项是transaction-isolation = REPEATABLE-READ - 会话级可临时改:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; - 改完立刻生效,但只影响当前连接,不影响其他已连会话
REPEATABLE READ 真的能彻底避免不可重复读吗
能,但有前提:必须显式开启事务(BEGIN 或 START TRANSACTION),且不能中途 COMMIT 或 ROLLBACK 后再查——否则新 SELECT 就属于下一个事务,自然读新快照。
常见误操作:
- 用 ORM(如 Django/SQLAlchemy)时未控制事务边界,
autocommit=True导致每条语句自成事务 - 在 MySQL 客户端里执行
SELECT前忘了BEGIN,以为“没关连接就还是同一个事务” - 应用层连接池复用连接,上一个请求的事务没清理干净,残留了未提交状态
验证方式:在事务内执行 SELECT 后,手动在另一终端更新该行并 COMMIT,再回原事务查——只要没退出事务,结果一定不变。
什么时候该用 READ COMMITTED 而不是 REPEATABLE READ
核心权衡点是「一致性 vs 并发性」:前者允许不可重复读但减少间隙锁、提升并发更新能力;后者靠 MVCC 快照保证一致性,但可能因间隙锁引发死锁或锁等待。
适用 READ COMMITTED 的真实场景:
- 日志类、统计类查询,不要求两次读绝对一致,只关心最新已提交状态
- 高并发订单更新,大量
UPDATE ... WHERE status = 'pending'类语句,在REPEATABLE READ下容易因间隙锁冲突 - 使用物理备份(如
mysqldump --single-transaction)时,若业务库本身设为READ COMMITTED,备份一致性仍由事务快照保障,不影响
注意:READ COMMITTED 下,InnoDB 的 next-key lock 退化为 record lock(不锁间隙),所以幻读风险上升——但「不可重复读」和「幻读」是两个问题,别混为一谈。
真正容易被忽略的是:隔离级别只是基础机制,事务是否真正「跨语句生效」,取决于你有没有让多条语句落在同一个事务上下文里。很多线上问题不是隔离级别选错了,而是事务根本没建起来。










