优先使用 UPDATE ... WHERE 原子校验,因其条件不满足时不加锁、不阻塞、无需事务控制且性能恒定;仅当业务逻辑无法单条 UPDATE 实现(如跨表校验、调外部接口)时才用 SELECT FOR UPDATE,并需注意索引、事务和锁持有时间。

直接用 UPDATE ... WHERE 原子校验,别写先查后更新——这是最快、最稳、也最容易被跳过的解法。
为什么 UPDATE ... WHERE 比 SELECT FOR UPDATE 更快
高并发下,SELECT FOR UPDATE 会立即加行锁,后续请求排队等待;而 UPDATE ... WHERE stock >= ? 是原子操作:条件不满足就整条语句不生效,不占锁、不阻塞、也不依赖事务隔离级别。
- 执行后立刻检查影响行数:
ROW_COUNT()返回 0 就代表失败(已被扣完或被别人抢走) - 无需建唯一索引、无需显式
BEGIN/COMMIT、也不怕连接池复用导致锁残留 - 在 MySQL 中,只要
WHERE条件能走索引,性能几乎恒定,不受并发量线性拖累 - 错误写法示例:
SELECT stock FROM inventory WHERE sku = 'A001'; IF @stock >= 5 THEN UPDATE inventory SET stock = stock - 5 WHERE sku = 'A001';—— 中间窗口期必然超卖
什么时候必须上 SELECT FOR UPDATE
仅当业务逻辑无法塞进单条 UPDATE 的 WHERE 条件里,比如要跨表查优惠券状态、调用外部风控接口、或需记录多步中间日志时,才考虑加锁。
-
SELECT ... FOR UPDATE必须在BEGIN事务内第一条执行,且WHERE字段(如sku)要有唯一索引,否则可能锁表 - 锁持有时间越长,阻塞越严重——别在锁住后做 HTTP 调用、字符串拼接或复杂计算
- 失败时不能只
SELECT 'error',必须SIGNAL SQLSTATE '45000'或ROLLBACK,否则事务挂着,后续语句仍可能提交“假成功” - MySQL 不支持
sp_getapplock,别用GET_LOCK()替代行锁——它不是事务安全的,一次异常退出就可能让锁永远挂住
临时表和批量更新场景下的并发陷阱
如果库存扣减要批量处理(比如一个订单含 20 个 SKU),别用表变量(@table)存待更新列表——优化器会误判为只有 1 行,生成低效的嵌套循环计划。
- 改用临时表(
#temp),并为连接字段(如sku)建聚集索引:INDEX IX_sku CLUSTERED (sku) - 批量
UPDATE时,确保主键是自增或有序,避免页分裂;千万级数据优先用LOAD DATA INFILE而非循环INSERT - 视图、
MAX(id)算新 ID、无索引JOIN这些操作,在并发下极易触发死锁或 S 锁争用,务必提前压测
真正卡住系统的,往往不是锁本身,而是锁住之后还做了不该做的事:等网络响应、拼接长字符串、写失败日志再提交……这些动作把毫秒级的行锁,拖成了秒级阻塞。原子 UPDATE 能绕开的,就别主动往锁里钻。











