select ... for update 在高并发下易成瓶颈,因其本质是行级写锁,但无索引时退化为表级锁或锁全索引范围,且事务未及时提交会导致锁长期持有。

为什么 SELECT ... FOR UPDATE 在高并发下容易成为瓶颈
它本质是加行级写锁,但若查询条件没走索引,InnoDB 会退化为表级锁或锁住整个索引范围;更常见的是事务未及时提交,导致锁长期持有。线上曾有服务因一个 FOR UPDATE 查询后忘记 COMMIT,阻塞了后续 200+ 请求。
实操建议:
启科网络商城系统由启科网络技术开发团队完全自主开发,使用国内最流行高效的PHP程序语言,并用小巧的MySql作为数据库服务器,并且使用Smarty引擎来分离网站程序与前端设计代码,让建立的网站可以自由制作个性化的页面。 系统使用标签作为数据调用格式,网站前台开发人员只要简单学习系统标签功能和使用方法,将标签设置在制作的HTML模板中进行对网站数据、内容、信息等的调用,即可建设出美观、个性的网站。
- 务必确认
WHERE条件字段有有效索引(用EXPLAIN验证key和rows) - 把
SELECT ... FOR UPDATE尽量靠近事务末尾,缩短锁持有时间 - 避免在事务中混入 HTTP 调用、日志 IO 或用户输入等待——这些都会让锁“悬停”
- 考虑用
SELECT ... LOCK IN SHARE MODE替代,如果业务允许读已提交的中间态
用 INSERT ... ON DUPLICATE KEY UPDATE 替代“查再改”逻辑
典型反模式是:先 SELECT 判断记录是否存在,再决定 INSERT 或 UPDATE。这在并发下必然产生竞态,且两次语句至少引入两轮锁竞争。
实操建议:
- 确保表有唯一索引(
UNIQUE KEY或PRIMARY KEY),否则ON DUPLICATE KEY UPDATE不生效 - 注意
UPDATE子句中不能引用被插入行的别名(MySQL 不支持VALUES(col)以外的上下文) - 该语句本身是原子的,只涉及一次索引查找和最多一次行锁,比“查-判-改”链路锁开销低一个数量级
慎用 SELECT COUNT(*) 做并发控制依据
在高并发场景下,用 SELECT COUNT(*) FROM t WHERE status = 1 判断是否达到阈值再放行,看似合理,实则危险:COUNT 可能触发全表扫描(尤其无合适索引时),且结果瞬时性极强,无法保证后续操作的原子性。
实操建议:
- 改用带版本号或状态机的预占机制,例如插入一条
status = 'pending'记录,再用UPDATE ... WHERE status = 'pending' AND id = ?确认生效 - 对计数类需求,单独维护一张轻量计数表(如
counter),用INSERT ... ON DUPLICATE KEY UPDATE value = value + 1原子增减 - 避免在事务中执行
COUNT(*)后 sleep 或等待——锁会一直挂着
事务隔离级别不是越高越好:READ COMMITTED 通常比 REPEATABLE READ 更适合写多场景
MySQL 默认的 REPEATABLE READ 依赖间隙锁(gap lock)防止幻读,但在高并发更新非唯一字段时,间隙锁范围难以预测,极易引发锁冲突甚至死锁;而 READ COMMITTED 只锁匹配到的行,不锁间隙。
实操建议:
- 确认业务能接受“同一事务内多次
SELECT结果可能不同”,就可安全降级到READ COMMITTED - 修改方式:连接建立后执行
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,或在应用层配置数据源默认隔离级别 - 降级后仍需注意:
UPDATE语句若WHERE条件无索引,依然会锁全表——隔离级别不解决索引缺失问题









