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

来源:这里教程网 时间:2026-02-28 20:52:13 作者:

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

脏读只会在

READ UNCOMMITTED
隔离级别下发生。其他三个级别(
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
)都通过不同机制阻止了脏读——不是靠“锁住所有东西”,而是靠 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)——这既是防止幻读的手段,也是死锁高发区。

相关推荐