不可重复读到底是什么现象
在 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(不锁间隙),所以幻读风险上升——但「不可重复读」和「幻读」是两个问题,别混为一谈。
真正容易被忽略的是:隔离级别只是基础机制,事务是否真正「跨语句生效」,取决于你有没有让多条语句落在同一个事务上下文里。很多线上问题不是隔离级别选错了,而是事务根本没建起来。
