mysql并发访问如何避免脏读_mysql隔离级别讲解

2次阅读

脏读只会在read uncommitted隔离级别下发生;该级别允许事务读取其他事务未提交的数据,而read committed、repeatable read和serializable均通过mvcc或锁机制避免脏读。

mysql并发访问如何避免脏读_mysql隔离级别讲解

脏读到底在什么隔离级别下会发生

脏读只会在 READ UNCOMMITTED 隔离级别下发生。其他三个级别(READ COMMITTEDREPEATABLE READSERIALIZABLE)都通过不同机制阻止了脏读——不是靠“锁住所有东西”,而是靠 MVCC(多版本并发控制)或行锁/间隙锁的组合。

mysql 默认是 REPEATABLE READ,所以只要没显式改过隔离级别,脏读就基本不会出现。但要注意:有些 ORM 或连接池初始化时会执行 SET session TRANSACTION ISOLATION LEVEL READ UNCOMMITTED,这种配置一开,脏读风险立刻回来。

如何确认当前会话的隔离级别

别猜,直接查。MySQL 提供两个关键变量:

  • @@tx_isolation(5.7.20+ 已弃用,但多数环境仍可读)
  • @@transaction_isolation(推荐,8.0+ 唯一标准)

执行以下命令即可:

select @@transaction_isolation;

返回值类似 'REPEATABLE-READ''READ-COMMITTED'。注意中间是短横线,不是下划线,拼错会报错。

如果看到 'READ-UNCOMMITTED',说明当前会话已主动降级,需检查应用层是否调用了 SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED

READ COMMITTED 和 REPEATABLE READ 的关键区别在哪

两者都不允许脏读,但对“不可重复读”和“幻读”的处理逻辑完全不同,直接影响你写业务逻辑时要不要加 SELECT ... for UPDATE

  • READ COMMITTED:每次 SELECT 都生成新快照,能读到其他事务已提交的最新数据 → 可能出现不可重复读
  • REPEATABLE READ(InnoDB 默认):事务启动时创建一致性视图(consistent read view),后续所有普通 SELECT 都复用该视图 → 不可重复读被屏蔽,但幻读仍可能在特定场景下发生(如未加锁的范围查询)

举例:你在事务中两次执行 SELECT count(*) FROM orders WHERE status = 'pending',若中间有其他事务插入并提交了一条新订单,在 READ COMMITTED 下第二次结果会变;在 REPEATABLE READ 下结果不变——但这不意味着幻读完全消失,UPDATE / delete 的 where 条件若匹配新增行,仍可能触发 gap lock 或 next-key lock。

并发更新时仅靠隔离级别不够,得配合锁语句

隔离级别解决的是“读一致性”,不是“写冲突”。比如两个事务同时执行:

UPDATE accounts SET balance = balance - 100 WHERE id = 1;

即使在 REPEATABLE READ 下,也**不会自动加行锁防止覆盖写**——InnoDB 确实会对匹配行加 X 锁,但前提是 WHERE 条件命中索引。如果 id 没建索引,会退化为表锁,性能崩盘;如果条件走不到索引(比如 WHERE JSON_CONTAINS(data, '"admin"')),那锁行为就不可控。

真正安全的做法是显式加锁:

  • 读后再更新:先 SELECT ... FOR UPDATE,再做计算和 UPDATE
  • 原子更新:直接用 UPDATE ... SET x = x + 1 WHERE ...,依赖 InnoDB 的当前读机制
  • 避免长事务:REPEATABLE READ 下长事务会拖住 purge 线程,导致 undo log 膨胀,最终撑爆 ibdata1

最常被忽略的一点:SELECT ... FOR UPDATE 在唯一索引等值查询时只锁匹配行;但在非唯一索引或范围查询时,会锁住间隙(gap lock),甚至锁住前后相邻的索引项(next-key lock)——这既是防止幻读的手段,也是死锁高发区。

text=ZqhQzanResources