应改用UPDATE语句模拟行锁竞争,配合多表大容量、忽略死锁错误、逐步调优线程数,并结合INNODB STATUS中等待锁、锁模式及读视图ID差分析锁瓶颈,避免范围查询引发间隙锁与超时设置过小导致重试风暴。

怎么用 sysbench 模拟真实写入压力测锁表现
直接跑 sysbench oltp_write_only 很容易误判,因为默认配置下它用的是 INSERT + 自增主键,几乎不触发行锁竞争。真正压测锁性能,得换成带唯一约束冲突、范围更新或等值更新的场景。
实操建议:
- 用
--oltp-tables-count=16和--oltp-table-size=1000000避免单表热点,更贴近线上分库分表后的小表压力 - 关键改写:把默认的
INSERT替成UPDATE sbtest1 SET k=k+1 WHERE id=?,并确保id是主键 —— 这样每条请求都走聚簇索引行锁,能暴露InnoDB的锁等待堆积问题 - 加
--mysql-ignore-errors=1205,1213,跳过死锁和锁等待超时错误,否则测试中途就断了;但要记得在结果里单独统计events (failed)里的死锁次数 - 并发线程数别从 100 起步,先试
--threads=8、16、32,观察queries per second是否开始下降、95th percentile latency是否陡升 —— 这才是锁争用开始起效的信号点
SHOW ENGINE INNODB STATUS 里哪几行真有用
很多人扫一眼 TRANSACTIONS 就关掉,其实真正反映锁瓶颈的是下面三块内容,且必须结合时间点看:
重点关注:
-
---TRANSACTION [hex], ACTIVE [n] sec后面有没有waiting for lock—— 不是“持有锁”,是“卡在等锁”,这才是阻塞源头 -
LOCK WAIT [n] lock struct(s)下方的lock_mode X locks rec but not gap类型,说明是行锁而非间隙锁,如果大量出现,说明业务逻辑集中在少数热 key 上(比如用户余额表的 top 10 用户) -
Trx read view will not see trx with id >= [id], sees <= [id]如果这个[id]和当前活跃事务 ID 差距极大,说明长事务拖慢了 purge 线程,间接抬高了锁等待概率
为什么 SELECT ... FOR UPDATE 在非唯一条件上特别危险
不是所有 FOR UPDATE 都只锁一行。当 where 条件没走唯一索引,InnoDB 可能升级为间隙锁(gap lock)甚至临键锁(next-key lock),锁住一片范围 —— 并发一高,后面所有满足条件的插入/更新全被堵住。
典型踩坑场景:
- 查用户订单用
WHERE status = 'pending',但status没建索引 → 锁全表 - 查时间范围用
WHERE created_at > '2024-01-01',即使有索引,也会锁住匹配范围后的第一个不满足记录(即间隙),导致新订单插入被阻塞 - 解决办法不是加索引就完事:对高频更新字段,优先建联合索引覆盖查询+更新路径;实在无法避免范围条件,考虑用
SELECT ... LOCK IN SHARE MODE+ 应用层重试,比强占排他锁更轻量
高并发写下 innodb_lock_wait_timeout 设太小反而更糟
设成 1 秒看起来很“及时”,但实际会让应用层频繁收到 Lock wait timeout exceeded,接着重试 → 更多事务进来抢锁 → 死锁率上升 → 整体吞吐不升反降。
调优原则:
- 先看业务容忍度:支付类事务可设 30–60 秒,日志类写入设 5 秒足够;别统一拍脑袋定 1 秒
- 配合
innodb_rollback_on_timeout=OFF(MySQL 5.7+ 默认开启),避免超时后自动回滚整个事务,造成部分成功部分失败的中间态 - 真正该监控的不是超时次数,而是
Innodb_row_lock_waits和Innodb_row_lock_time_avg—— 前者持续上涨说明锁冲突变多,后者超过 50ms 就该怀疑是不是索引失效或热点 key 出现了
锁不是越快释放越好,是让等待更可预测;而最常被忽略的,其实是事务里混进了 SLEEP()、远程 HTTP 调用、或大对象序列化 —— 这些操作把锁持有时间从毫秒拉到秒级,比任何参数调整都致命。



















