mysql 8.0 不支持多线程更新同一行,高并发下 update ... set x = x + 1 会因 innodb 行锁串行化导致严重排队;有效解法包括:用 redis 原子操作+异步落库、insert ... on duplicate key update 合并写、分桶分散热点。

MySQL 8.0 本身不提供“多线程更新同一行”的能力,强行用多线程并发执行 UPDATE ... WHERE id = ? 只会加剧锁排队,不是优化而是恶化。 真正有效的解法是绕开“多线程争抢单行”这个死结,把原子性写操作从 MySQL 层前移到更轻量的载体(如 Redis),再按需批量、异步、合并落库。
为什么 UPDATE ... SET x = x + 1 WHERE id = 1 在高并发下必然卡住
InnoDB 的行锁是真实串行化屏障:所有事务都得排队等 id = 1 这一行的 X 锁。哪怕只是加 1,只要并发请求远超单行吞吐(通常 200–500 QPS 就开始明显排队),innodb_row_lock_waits 就会飙升,Threads_running 暴涨但实际 QPS 却上不去。CPU 不一定高,但锁等待本身会触发频繁上下文切换和检测开销。
常见错误现象包括:Lock wait timeout exceeded、慢日志里大量该语句耗时 >100ms、监控显示 innodb_row_lock_time_avg 持续升高。
- 不要在事务里做 HTTP 调用或文件 IO,这等于主动拉长锁持有时间
- 避免
SELECT ... FOR UPDATE+ 应用层计算 +UPDATE三段式,它把锁范围从“写瞬间”扩大到“读+算+写全过程” - 确认业务是否真需要强一致性——点赞数、点击量、埋点计数大多可接受最终一致
用 INSERT ... ON DUPLICATE KEY UPDATE 合并多次小更新
这是 MySQL 原生最轻量的“无显式锁合并写”方案,本质是靠唯一键冲突触发更新,跳过 SELECT 加锁流程。
适用前提:user_counter 表必须有 PRIMARY KEY 或 UNIQUE KEY(如 user_id),且你能在应用层聚合相同 key 的增量。
INSERT INTO user_counter (user_id, click_count) VALUES (123, 1), (456, 1), (123, 1), (789, 1) ON DUPLICATE KEY UPDATE click_count = click_count + VALUES(click_count);
-
VALUES(click_count)是关键:它取的是当前这一行 INSERT 值(即 1),不是整条语句所有值的总和 - 不支持 WHERE 条件判断(比如“余额 > 100 才扣减”),只适合纯累加类场景
- 单次批量最多 1000 行(受
max_allowed_packet和网络包限制),建议控制在 100–500 行以内
用 Redis 原子操作 + 异步消费队列降频落库
当热点更新频率超过 MySQL 单行吞吐极限(例如每秒上千次同 key 更新),就必须把写操作前置到内存层。
典型链路:DECRBY stock:1001 1 快速校验并扣减 → 成功则 LPUSH update_queue '{"item_id":1001,"delta":-1}' → 后台消费者 BRPOP 批量聚合 → 拼成一条 INSERT ... ON DUPLICATE KEY UPDATE 或 REPLACE INTO 刷入 MySQL。
- Redis 的
DECRBY是原子的,能扛住 5W+/秒,天然解决超卖和锁竞争 - 后台消费者速率要可控(如固定 300 QPS),避免把压力又全压回 MySQL
- 注意消息丢失风险:Redis 队列需开启 AOF + RDB 持久化,消费者需幂等处理
分桶(Sharding)让“一行热点”变成“多行分散”
如果业务逻辑允许弱一致性,最直接的数据库层改造就是把单行拆成 N 行,用哈希或随机方式路由写请求。
例如库存表从 stock(id=1, value=1000)改为 stock_shard(item_id, shard_id, value),初始化时把 1000 拆成 10 份,每份 100;扣减时 shard_id = RANDOM() % 10,查询总库存用 SUM(value) FROM stock_shard WHERE item_id = 1。
- 分桶数不宜过多(一般 8–32),否则
SUM查询变慢;也不宜过少( - 必须保证所有写请求走同一套分桶规则,否则数据会错乱
- 不适用于需要精确行级条件判断的场景(如“扣减前必须查当前值是否 ≥ delta”)
真正容易被忽略的是:**所有这些方案的前提,是你得先确认那行是不是真热点**——用 SHOW ENGINE INNODB STATUS 查 TRANSACTIONS 部分,或看 performance_schema.data_locks,别靠猜。











