mysql并发读写会产生哪些问题_mysql常见并发场景说明

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

脏读、不可重复读、幻读到底怎么触发?

这三个问题不是理论概念,而是真实 SQL 执行顺序下必然出现的现象。关键在于:**事务 A 读取时,事务 B 正在做什么、是否已提交、操作的是同一行还是新插入的行**。

脏读:事务 A 执行
SELECT
时,事务 B 刚
UPDATE
了一行但还没
COMMIT
;A 读到了这行“未定稿”,B 后续
ROLLBACK
→ A 的读取结果就失效了。
不可重复读:事务 A 第一次
SELECT id=100
得到
name='Alice'
;事务 B 提交了
UPDATE users SET name='Bob' WHERE id=100
;A 再次
SELECT id=100
,得到
name='Bob'
—— 同一行值变了。
幻读:事务 A 执行
SELECT * FROM orders WHERE status='pending'
返回 3 条;事务 B 插入一条新
status='pending'
记录并
COMMIT
;A 再次执行相同查询,返回 4 条 —— 行数变多了,像“幻影”。注意:这不是更新同一行,而是满足条件的新行被插入。

MySQL 默认的 REPEATABLE READ 真的能防住所有问题吗?

不能。MySQL InnoDB 的默认隔离级别是

REPEATABLE READ
,它靠 MVCC + Next-Key Lock(临键锁)实现,能防脏读和不可重复读,但对幻读只做“部分防护”——仅针对普通
SELECT
(快照读)有效;一旦用了当前读(如
SELECT ... FOR UPDATE
UPDATE
DELETE
),幻读仍可能发生,尤其在范围条件上。

例如:事务 A 执行
SELECT * FROM t WHERE c > 10 FOR UPDATE
锁住满足条件的记录和间隙;事务 B 尝试插入
c=15
会被阻塞 → 这是幻读被锁挡住。
但如果事务 B 插入的是
c=5
(不在 A 的查询范围内),或 A 没加锁直接
SELECT
(快照读),那 B 的插入就能成功,A 再查就会“看到幻影”。
真正彻底解决幻读,只有
SERIALIZABLE
隔离级别,但它会让所有并发
SELECT
变成串行,线上基本不用。

写-写冲突:为什么两个 UPDATE 会互相卡住?

当两个事务同时想改同一行,InnoDB 必须用排他锁(X 锁)互斥。谁先拿到锁谁先改,后到的只能等 —— 这就是锁等待。如果等待超时(默认

innodb_lock_wait_timeout = 50
秒),会报错:
Lock wait timeout exceeded; try restarting transaction

典型场景:秒杀扣库存,多个请求同时执行
UPDATE goods SET stock = stock - 1 WHERE id = 123 AND stock > 0
陷阱:即使 SQL 带了
AND stock > 0
条件,InnoDB 仍会对匹配的索引记录(甚至间隙)加 X 锁,后续请求必须排队。
优化方向不是“去掉锁”,而是缩短锁持有时间:确保该语句走索引、避免大事务、减少其他无关 SQL 在同一事务中。

读-写并发下,MVCC 是怎么悄悄帮你躲开锁的?

MVCC 不是魔法,它是通过给每行数据维护多个版本(由

DB_TRX_ID
标记),配合事务启动时生成的
ReadView
,让普通
SELECT
读取“快照”,而不是最新行 —— 所以读不加锁,也不阻塞写。

关键点:
REPEATABLE READ
下,事务第一次
SELECT
生成
ReadView
,之后所有快照读都复用它;
READ COMMITTED
则每次
SELECT
都新建
ReadView
,所以能看到其他事务已提交的修改(即允许不可重复读)。
注意:
SELECT ... LOCK IN SHARE MODE
FOR UPDATE
是当前读,绕过 MVCC,直接加 S/X 锁,会阻塞其他写操作。
一个易忽略的事实:MVCC 只解决读-写冲突,对写-写冲突完全不管 —— 两个
UPDATE
依然要抢锁,哪怕它们读的是不同快照。

最常被低估的一点:并发问题从来不是孤立存在的。比如你调高了隔离级别防幻读,却没意识到它会让更多语句升级为当前读,从而增加锁竞争;又比如你依赖 MVCC 实现无锁读,却在事务里混进了

SELECT ... FOR UPDATE
,瞬间打破快照一致性。真正的并发控制,是隔离级别、锁策略、SQL 写法、应用重试逻辑四者咬合的结果,缺一不可。

相关推荐