select … for update 会阻塞其他事务对同一行的 update、delete 和再次 select … for update 操作,但普通 select(mvcc 快照读)不受影响;若查询未走索引,则可能退化为范围锁甚至表锁,导致意外阻塞。

SELECT … FOR UPDATE 会阻塞其他事务的哪些操作?
不是所有读写都会被阻塞,但容易误判。只要另一个事务尝试对同一行执行 UPDATE、DELETE 或再次用 SELECT … FOR UPDATE,就会进入锁等待;而普通 SELECT(不加锁)在 RR 隔离级别下仍可读取快照,不受影响。
真正危险的是“隐式升级”:当查询条件未命中索引,InnoDB 无法精确加行锁,会退化为临键锁(Next-Key Lock),实际锁住一个范围——可能让本不该冲突的业务操作互相卡住。
- 没走索引的
WHERE status = 'pending'可能锁全表或大片主键区间 -
ORDER BY + LIMIT场景下,即使只更新一行,也可能锁住排序路径上的多行 - 事务中先
SELECT … FOR UPDATE再做业务判断,若判断后不更新,锁却一直持有到事务结束
微服务间事务边界不一致如何放大锁等待?
单体应用里事务通常短平快,微服务则天然跨进程、跨数据库甚至跨语言。一个下单流程可能涉及库存服务(扣减)、订单服务(创建)、支付服务(预占),每个服务开启自己的本地事务,靠最终一致性兜底。但若库存服务用了 SELECT … FOR UPDATE 锁行,而订单服务因网络抖动或重试逻辑导致事务迟迟不提交,这个锁就卡在那儿不动。
更隐蔽的问题是“锁持有时间不可控”:RPC 超时设置、下游服务响应慢、中间件重试策略,都会让原本毫秒级的锁持有变成秒级甚至分钟级。
- 服务 A 调用服务 B,B 执行了
SELECT … FOR UPDATE后调用服务 C,C 响应延迟 → A 的锁等待链被拉长 - 不同服务使用不同事务超时配置:
innodb_lock_wait_timeout是 50 秒,但 Spring 的@Transactional(timeout=3)提前回滚,导致锁释放滞后 - 服务间缺乏统一的锁监控视图,DBA 看到
SHOW ENGINE INNODB STATUS里一堆 waiting for lock,却无法关联到具体哪个微服务哪条 trace ID
为什么乐观锁替代方案在高并发写场景下也未必可靠?
乐观锁靠版本号或时间戳实现,避免了长时间持锁,但写冲突概率随并发上升而陡增。尤其在秒杀类场景,大量请求同时读取同一行(如商品库存),再比对版本后更新,失败率可能超过 80%,重试成本本身就成了性能瓶颈。
关键陷阱在于“读-改-写”过程中的非原子性:即使用了 UPDATE t SET stock = stock - 1, version = version + 1 WHERE id = 1 AND version = ?,如果业务层在 UPDATE 前做了额外校验(比如检查是否已售罄),这部分逻辑不在 SQL 原子范围内,仍需配合悲观锁或应用层分布式锁,反而叠加复杂度。
- 版本号字段必须有索引,否则
WHERE version = ?全表扫描 → 触发表锁 - 重试逻辑若无退避策略(如指数退避),会加剧数据库连接和 CPU 压力
- 跨库更新时,无法用单条 SQL 保证多表版本一致性,只能退回到分布式事务或补偿机制
真正要盯住的不是锁类型,而是锁的“暴露面”
微服务架构下,锁问题从来不是孤立的 SQL 问题,而是服务治理问题。一个 SELECT … FOR UPDATE 在单体里可能只影响几百 QPS,在微服务里可能通过 API 网关被放大成每秒数千次锁请求,且来源分散、链路难追溯。
最容易被忽略的是锁粒度与业务语义的错配:比如用库存表的行锁保护“用户下单资格”,但资格判断其实依赖用户等级、优惠券、风控结果等多个外部状态——这些状态变化不触发锁,却让锁失去业务意义,纯属空等。
上线前必须确认:该锁是否真的覆盖了所有并发冲突路径?有没有更轻量的协调方式(如 Redis 原子计数器 + 最终一致性)?锁等待是否会被链路追踪系统自动采集并告警?











