优惠券领取接口用 select for update 做幂等,压测到两百并发时全部卡住,processlist 里一堆锁等待,SQL 条件是 status=0,status 只有两个取值。

一开始怀疑是「事务里夹了远程调用,持锁时间被拉到几百毫秒」,查了一圈,方向不对。

把日志和配置对完,能解释这个现象的基本就这几处。where 条件字段没有索引,扫到的每一行都被加锁。还有一种是status 只有两个值,索引选择性太差等于全表。

另外,共享锁和排他锁混用,互相等对方的锁。

上来先看当前锁等待的持有关系

mysql -e "select * from performance_schema.data_locks\G" | head -40

然后换成按唯一键定位的写法

mysql -e "select id from coupon where code='X2026' for update;"

随后把唯一索引补上

mysql -e "alter table coupon add unique index uk_code(code), algorithm=inplace;"

缩短事务,把外部调用挪到事务外

mysql -e "select trx_id,trx_state,trx_started from information_schema.innodb_trx;"

恢复的判定其实很直接。压测下不再出现大面积锁等待,领取接口 P99 从三秒降到四十毫秒,重复领取的拦截依然有效。

for update 锁的是扫描到的行,没有索引就等于全表;高并发幂等更推荐唯一索引加 insert ignore,避免显式加锁。如果同一现象隔几天又出现,说明根因没找到,别当偶发处理。

作者 9823189