MySQL 热点行更新为什么会成为瓶颈?如何优化库存和计数器场景?
简化版
热点行是大量并发请求更新同一行,例如秒杀库存、文章点赞数、账户余额。InnoDB 行锁会让同一行更新串行化,导致锁等待、吞吐下降和延迟抖动。优化思路包括减少同步更新频率、拆分热点、库存分桶、异步汇总、乐观扣减、队列串行化、缓存前置和限流。关键是不要让所有流量都打到数据库同一行。
详细版
数据库擅长保证一致性,但同一行的写冲突无法无限并发。比如 UPDATE stock SET quantity = quantity - 1 WHERE sku_id = 1 AND quantity > 0,语义正确,但所有人都抢同一个 sku_id 时,行锁会排队。
优化要先判断业务一致性要求。余额和支付强一致,不能随便异步;点赞数、浏览量可以延迟汇总;库存可以分桶或预扣。不同场景的优化方式不同。
面试中要把“热点行是锁竞争问题”讲清楚,而不是只说加索引。
完整版教学
一、热点行是什么
热点行是被大量并发访问的同一行。
读热点可以靠缓存缓解。
写热点更麻烦,因为更新需要互斥。
InnoDB 对同一行更新会加行锁。
并发越高,等待队列越长。
二、典型 SQL
UPDATE sku_stock
SET quantity = quantity - 1
WHERE sku_id = 1001
AND quantity > 0;
这条 SQL 可以保证不超卖。
但高并发下所有请求都更新同一行。
最终吞吐受单行更新能力限制。
三、常见场景
| 场景 | 一致性要求 | 优化方向 |
|---|---|---|
| 秒杀库存 | 高 | 分桶、预扣、队列、限流 |
| 点赞数 | 中低 | 缓存计数、异步落库 |
| 浏览量 | 低 | 批量汇总 |
| 账户余额 | 极高 | 严格事务、减少并发入口 |
| 排行榜计数 | 中 | Redis 聚合后周期落库 |
四、库存分桶
把一个商品库存拆成多个库存桶。
请求按随机或哈希选择桶扣减。
多个桶分散写入压力。
最终库存是多个桶之和。
缺点是实现复杂度和回收逻辑增加。
五、异步汇总
点赞、浏览、曝光这类指标不一定要实时强一致。
可以先写缓存或消息队列。
再按批次汇总到 MySQL。
这样减少单行高频更新。
但要处理丢消息、重复消费和最终一致性。
热点行优化的本质是把“单点串行写”改成“分散写、批量写或限流写”。
六、数据库侧注意点
索引要保证 UPDATE 精确定位行。
事务要尽量短。
避免在持锁期间调用外部服务。
观察锁等待和死锁日志。
热点写入要结合业务限流,不能只靠数据库硬扛。
七、误区和追问
- 误区:热点行加索引就能解决。 索引只能更快定位行,不能消除同一行更新互斥。
- 误区:库存扣减可以随便异步。 库存涉及超卖风险,要看业务能否接受预扣和补偿。
- 误区:Redis 计数就完全不用落库。 仍要考虑持久化、对账和恢复。
- 追问:余额场景能分桶吗? 通常不适合随意分桶,余额强一致要求更高。
- 追问:库存分桶有什么问题? 桶间不均、剩余库存回收、查询总量和补偿逻辑更复杂。
- 追问:如何发现热点行? 看锁等待、慢 SQL、同一主键更新频率和业务访问分布。
八、面试收束
回答时先指出它是行锁竞争。
再按业务一致性选择分桶、异步、队列或限流。
最后强调强一致场景不能为了性能破坏正确性。