锁竞争根源在于锁不准、锁太久、锁不稳,需精准索引、短事务、拆分热点行并合理设置隔离级别。

锁竞争过大,核心不是锁本身太强,而是锁得不准、锁得太久、锁得不稳。真正有效的缓解方式,是让锁尽快加、精准加、尽早放——这既靠架构设计,也靠代码写法。
索引必须精准匹配查询条件
WHERE 条件没走索引,InnoDB 就会全表扫描,对每行加意向锁甚至行锁,本质是把行锁退化成表级争用。这不是并发高导致的,是 SQL 写错了。
- 用 EXPLAIN 看 type 字段:出现 ALL 或 index 必须优化,不能上线
- 避免隐式类型转换:WHERE user_id = '123'(user_id 是 INT)→ 改成 WHERE user_id = 123
- 别用函数包裹索引字段:如 WHERE DATE(create_time) = '2026-05-01' → 改成 WHERE create_time >= '2026-05-01' AND create_time
- 联合索引注意最左前缀:建了 (status, category),但只查 WHERE category = 'A',照样全表扫
事务要短、纯、可控
事务从 BEGIN 到 COMMIT 的每一毫秒,都在持续持有锁。日志、HTTP 调用、MQ 发送、文件写入这些非数据库操作,一旦混进事务,锁就“挂”在那里不动。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 把通知类逻辑全部移出事务,改用异步任务或最终一致性补偿
- 批量更新必须拆小批:比如 10 万条,用 UPDATE ... WHERE id IN (1,2,...,500) 分页提交,单批控制在 100–500 行
- 避免“先 SELECT 再 UPDATE”:直接用 SELECT ... FOR UPDATE WHERE id = ? 一步到位,且必须带 ORDER BY id ASC 防死锁
- 监控长事务:SELECT * FROM information_schema.INNODB_TRX,重点关注 trx_started 早、trx_state = 'RUNNING' 的记录
隔离级别与锁策略要按需调整
默认的 REPEATABLE READ 启用间隙锁,更新 ID=100 可能顺带锁住 ID=95~105 整个空隙,其他事务插入或更新 nearby 数据全被堵住。READ COMMITTED 关闭间隙锁,只锁命中行,释放更及时。
- 确认业务可接受“不可重复读”:比如订单详情页两次刷新看到不同状态,多数场景完全没问题
- 必须配合 binlog_format = 'ROW',否则主从复制异常;查看命令:SHOW VARIABLES LIKE 'binlog_format';
- 全局设置才生效:SET GLOBAL transaction_isolation = 'READ-COMMITTED';(仅会话级设置无效)
- 不要盲目加 SELECT ... FOR UPDATE:它可能引发更重锁,前提是 WHERE 条件必须走有效索引;执行前先 EXPLAIN 确认 key 和 rows 是否合理
热点行必须打散,不能硬扛
秒杀库存、用户积分、计数器这类单行高频更新,天然就是瓶颈。InnoDB 的聚簇索引让物理位置固定,数据分布无法摊薄压力。
- 改表结构:把单行库存拆成多行,例如 goods_stock_shard (goods_id, shard_id, stock),主键为 (goods_id, shard_id)
- 应用层路由:扣减时随机或轮询选一个 shard_id,把压力分散到 N 行
- 读取汇总值时,用 SUM(stock) 聚合,不影响一致性
- 避免自增主键 + 单热点更新组合,这是锁雪崩最典型的温床










