当前位置:首页 > 问答 > 正文

数据库管理 数据并发控制 mysql悲观锁下还能读取数据吗

MySQL悲观锁下还能读取数据吗?深入解析并发控制的那些事儿

2025年7月最新动态
近期MySQL 8.3版本优化了InnoDB引擎的锁机制,官方文档特别强调悲观锁在特定场景下的读取行为可能受事务隔离级别影响,开发者社区热议「是否所有悲观锁都会彻底阻塞读取」成为高频技术话题。


先搞懂基础概念

什么是悲观锁?

悲观锁(Pessimistic Lock)就像个「被害妄想症患者」——它默认并发操作一定会冲突,所以只要抢到锁就死死抱住不撒手,直到事务结束,MySQL中典型的悲观锁实现包括:

  • SELECT ... FOR UPDATE(写锁)
  • SELECT ... LOCK IN SHARE MODE(读锁,已逐渐被淘汰)

关键问题拆解

「悲观锁下能否读取数据」的本质其实是:

数据库管理 数据并发控制 mysql悲观锁下还能读取数据吗

  • 来读?(当前事务还是其他事务)
  • 怎么读?(普通SELECT还是加锁读)
  • 隔离级别是什么?(RC、RR等)

不同场景实测表现

场景1:当前事务内读取(能读!)

-- 事务A
BEGIN;
SELECT * FROM accounts WHERE id=1 FOR UPDATE; -- 加悲观锁
SELECT * FROM accounts WHERE id=1; -- 可以正常读到数据(甚至不需要COMMIT)
COMMIT;

✅ :悲观锁不会阻塞当前事务的普通查询,因为锁本来就是你自己加的。

场景2:其他事务尝试读取(看隔离级别!)

情况① 使用READ COMMITTED隔离级别
-- 事务A加锁
BEGIN;
SELECT * FROM accounts WHERE id=1 FOR UPDATE;
-- 事务B尝试读取(不同会话)
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT * FROM accounts WHERE id=1; -- 能读到旧数据!

✅ :RC级别下,其他事务的普通SELECT不受悲观锁影响(但读到的是锁前的快照)。

情况② 使用REPEATABLE READ隔离级别
-- 事务A加锁(同上)
-- 事务B设置RR级别
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM accounts WHERE id=1; -- 依然能读到旧数据

✅ :RR级别表现与RC类似,但底层通过MVCC机制实现(读取视图版本不同)。

场景3:其他事务尝试加锁读(必阻塞!)

-- 事务A持有锁(同上)
-- 事务B尝试加锁
SELECT * FROM accounts WHERE id=1 FOR UPDATE; -- 卡住直到超时!

❌ :只要其他事务想获取同一条记录的悲观锁,一定会被阻塞。

数据库管理 数据并发控制 mysql悲观锁下还能读取数据吗


底层原理速览

MySQL的锁与MVCC联合作业

  • 悲观锁:在记录上打标记(X锁),物理阻止其他事务修改
  • MVCC:多版本并发控制,允许读操作绕过锁访问历史版本

为什么普通SELECT能读?

InnoDB默认所有普通SELECT都是快照读(Snapshot Read),它:

  • 不申请锁
  • 读取Undo Log中的合适版本
  • 完全无视任何正在持有的锁

开发避坑指南

该用悲观锁的场景

  • 余额扣减(防止超卖)
  • 订单状态变更(避免重复处理)

错误用法警告

-- 错误示范:以为加了锁就能阻止别人读
SELECT * FROM orders WHERE status='pending' FOR UPDATE;
-- 其他事务依然能看到pending订单(只是不能修改)

终极解决方案

如果需要完全不可见的效果:

  1. 改用Serializable隔离级别(性能杀手)
  2. 应用层二次校验(例如Redis分布式锁)

发表评论