根本原因是x锁串行排队,非索引或配置问题;高并发update同一行必然排队,需分段更新、redis预扣减和合并请求来缓解。

MySQL在高并发更新同一行数据时CPU飙升,不是配置没调好,而是InnoDB行锁机制+事务串行化执行天然导致的资源争用放大。所有请求卡在同一行X锁上,CPU被大量空转等待和上下文切换吃掉。
为什么UPDATE同一行会变成串行执行
InnoDB的行锁是“乐观+悲观”混合实现:写操作必须获取该行的排他锁(X锁),而这个锁不能共享。当1000个并发请求同时执行UPDATE products SET stock = stock - 1 WHERE id = 123时:
- 第一个事务成功加锁并执行,其余999个全部阻塞在
waiting for trx_id状态 - 每个等待线程都在内核态轮询锁状态,持续消耗CPU(不是休眠)
- 事务提交越慢(比如有触发器、大事务、慢日志刷盘),锁持有时间越长,排队雪球越大
-
SHOW ENGINE INNODB STATUS里能看到几十甚至上百个LOCK WAIT,但Threads_running可能只有1–2个——CPU花在调度和等待上,而非实际计算
innodb_row_lock_time_avg飙升但慢日志不记录的原因
锁等待本身不产生慢查询日志条目,因为SQL语法合法、执行计划也正常,只是被卡住了。真正耗时的是锁等待,而非SQL解析或执行:
-
innodb_row_lock_time_avg从几微秒跳到毫秒级,说明锁争用已成瓶颈 -
slow_query_log里查不到对应语句,因为单条UPDATE执行本身可能只要0.2ms,但总耗时98%花在等锁 - 监控中
CPU usage高 +QPS低 +Threads_connected高,是典型锁竞争特征 - 不要只看
EXPLAIN,要结合performance_schema.data_locks和events_statements_history_long定位谁在持锁
为什么加索引、调innodb_buffer_pool_size都救不了这行热点
这类问题跟索引是否命中、缓存是否充足无关——锁冲突发生在内存中的锁结构(lock_sys_t)层面,与磁盘I/O或缓冲池无关:
- 即使该行已在
innodb_buffer_pool中热住,锁还是得抢 - 加索引只能加速WHERE定位,但定位完还是要抢同一行的X锁
- 增大
innodb_buffer_pool_size对锁竞争零缓解,反而可能因内存压力引发swap,进一步拖慢事务提交 - 唯一有效路径是打破“单行”这个物理热点,例如库存分片(sharding by virtual slots)、异步队列削峰、或改用Redis原子计数+最终一致性校验
真正容易被忽略的是:你看到的“CPU高”,大概率不是SQL在算,而是MySQL在反复检查“那个锁松开了没有”。优化方向不在SQL本身,而在让请求别挤在同一个门框里排队。











