MySQL FOR SHARE 怎样在一致性读中使用共享锁
来源:17golang原创
时间:2026-10-28 23:09:07 389浏览 收藏
当业务需要“先确认一行记录,再在同一个事务里继续处理”时,普通 SELECT 只解决读取问题,并不阻止别的事务修改这行。InnoDB 可以用 SELECT ... FOR SHARE 做锁定读:读取目标行并加共享锁,其他事务仍可读取,但在当前事务提交前不能修改这些行。
官方地址:https://dev.mysql.com/doc/refman/8.4/en/innodb-locking-reads.html
先记住一个判断:只想得到一个一致的快照,用普通 SELECT;需要把“检查结果”保护到后续业务动作,用 FOR SHARE。锁只在事务边界内有意义,不能把它当成查询末尾的装饰语句。
FOR SHARE 解决的不是“读不到”,而是“读完被改了”
普通一致性读会基于 InnoDB 的多版本机制读取某个时间点的快照,不主动给行加锁。它适合报表、详情页和不准备马上修改的数据读取。问题出现在“检查库存、校验状态、读取配置后还要继续处理”的场景:另一个事务可能在检查完成后修改同一行。
FOR SHARE 是锁定读。它读取目标行,并为这些行建立共享锁;其他会话可以继续读取,但不能修改或删除这些行,直到持锁事务提交或回滚。若目标行刚被另一个事务修改但尚未提交,当前锁定读会等待,并在对方事务结束后读取最新值。

在同一个事务中写出最小可用语句
下面用账户表模拟“先读取状态,再决定是否继续”的场景。关键点有两个:表使用 InnoDB,并且锁定读与后续动作处于同一个事务中。
-- 建立一个使用 InnoDB 的最小示例表
CREATE TABLE account_demo (
id BIGINT PRIMARY KEY,
status VARCHAR(16) NOT NULL,
balance DECIMAL(12, 2) NOT NULL
) ENGINE = InnoDB;
-- 共享锁必须和后续业务判断处于同一个事务
START TRANSACTION;
-- 读取账户当前状态,并阻止其他事务修改该行
SELECT id, status, balance
FROM account_demo
WHERE id = 1001
FOR SHARE;
-- 这里根据读取到的状态执行只读检查或后续业务动作
-- 所有动作完成后释放共享锁
COMMIT;
如果只是把 FOR SHARE 放在一个自动提交的单条语句后面,语句结束时事务也可能随之结束,锁的保护范围就很短。需要跨越多条语句保护判断结果时,显式使用 START TRANSACTION、COMMIT 或 ROLLBACK。
普通 SELECT 和 FOR SHARE 的边界怎么选
两种写法都能返回数据,但它们表达的是不同的并发意图。普通 SELECT 关注“我看到哪个快照”,FOR SHARE 关注“我在后续处理前不允许别人改这几行”。不要因为查询看起来只是读取,就默认两者等价。
| 写法 | 读取语义 | 是否阻止并发修改 | 适用场景 |
|---|---|---|---|
SELECT ... | 普通一致性读,读取快照 | 否 | 展示、报表、无需保护的查询 |
SELECT ... FOR SHARE | 锁定读,读取并加共享锁 | 当前事务提交前阻止修改 | 检查后继续处理、保护状态判断 |
SELECT ... FOR SHARE NOWAIT | 尝试立即获得共享锁 | 冲突时立即报错 | 接口不愿意等待的快速失败场景 |
SELECT ... FOR SHARE SKIP LOCKED | 跳过已经被锁住的行 | 不等待冲突行 | 允许换取其他可处理记录的队列读取 |
FOR SHARE 是旧式 LOCK IN SHARE MODE 的替代语法,后者仍可用于兼容旧代码;新代码优先使用 FOR SHARE,因为它支持 OF table_name、NOWAIT 和 SKIP LOCKED 选项。

遇到并发写入时,等待行为来自哪里
可以用两个会话理解等待边界。会话 A 先修改但不提交,会话 B 再执行 FOR SHARE。B 不是“读不到数据”,而是在等待 A 对同一行的未提交修改结束;A 提交后,B 才继续读取最新值。
-- 会话 A:持有未提交的排他修改 START TRANSACTION; UPDATE account_demo SET status = 'locked' WHERE id = 1001; -- 暂不 COMMIT,让其他会话观察锁等待 -- 会话 B:锁定读取同一行,默认会等待会话 A 结束 START TRANSACTION; SELECT id, status, balance FROM account_demo WHERE id = 1001 FOR SHARE; -- 会话 A 提交或回滚后,会话 B 才能继续 COMMIT;
在接口请求不适合长时间等待时,可以使用 NOWAIT。它把锁冲突转成即时错误,应用层可以返回“稍后重试”或选择降级。批量领取任务时,如果允许跳过繁忙记录,则使用 SKIP LOCKED,但必须接受结果集可能暂时不完整。
-- 冲突时立即失败,避免请求线程长时间等待 SELECT id, status FROM account_demo WHERE status = 'ready' FOR SHARE NOWAIT; -- 允许跳过已被其他事务锁住的行,适合可换取下一条记录的场景 SELECT id, status FROM account_demo WHERE status = 'ready' ORDER BY id LIMIT 10 FOR SHARE SKIP LOCKED;
使用前检查四个容易忽略的条件
- 确认存储引擎。
FOR SHARE的事务锁语义针对 InnoDB;不要只看表名就假定所有引擎行为相同。 - 确认事务生命周期。连接池复用时,必须确保成功路径、异常路径都提交或回滚,避免把锁带到下一次请求。
- 确认查询范围。给过滤条件和排序条件准备合适索引,缩小锁定读需要访问的范围,减少等待和死锁机会。
- 确认权限与降级语义。
FOR SHARE需要SELECT权限;NOWAIT与SKIP LOCKED不是“更快的普通查询”,它们分别改变冲突和结果集的处理方式。
排查时可以先确认当前事务隔离级别,再用两个会话复现是否等待。不要在业务高峰临时扩大事务范围,也不要用 SKIP LOCKED 掩盖本应处理的冲突;它适合明确允许跳过的任务分配场景。
常见问题
FOR SHARE 会阻止其他事务读取吗?
不会。共享锁允许其他会话读取,但不允许它们在当前事务提交前修改或删除被锁定的行。具体结果仍受事务隔离级别、访问条件和存储引擎影响。
为什么执行 FOR SHARE 仍然读到了旧值?
先确认使用的是锁定读语法,而不是普通 SELECT,再确认是否存在尚未结束的并发事务。普通一致性读读取快照;锁定读遇到未提交修改时会等待,并在对方事务结束后读取最新值。
FOR SHARE 和 LOCK IN SHARE MODE 要怎么选?
两者是等价语义,LOCK IN SHARE MODE 主要用于兼容旧代码。新代码使用 FOR SHARE,因为表达更清晰,也能配合 OF、NOWAIT 和 SKIP LOCKED。
结论可以收缩成一句话:普通 SELECT 解决一致性读取,FOR SHARE 解决读取后继续处理时的共享锁保护。把它放进明确的事务边界,再根据是否允许等待选择默认行为、NOWAIT 或 SKIP LOCKED,才能让并发语义与业务目标一致。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
257 收藏
-
369 收藏
-
191 收藏
-
499 收藏
-
209 收藏
-
400 收藏
-
308 收藏
-
423 收藏
-
161 收藏
-
500 收藏
-
121 收藏
-
188 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习