直接UPDATE热点表会卡住查询,因未走索引或大范围更新触发间隙锁/表锁,且MVCC需维护大量undo log,高并发下易超时或拖慢SELECT。

为什么直接 UPDATE 热点表会卡住查询?
因为 InnoDB 默认用行锁,但若 WHERE 条件没走索引、或更新范围大,就会升级成间隙锁甚至表级锁;同时 MVCC 的一致性读需要维护大量 undo log 版本,高并发下容易触发 Lock wait timeout exceeded 或显著拖慢 SELECT 响应。
用 INSERT ... ON DUPLICATE KEY UPDATE 替代 UPDATE 的前提和限制
这招本质是“写入优先”,靠唯一键冲突触发更新,能绕过部分锁等待,但只适用于有明确唯一约束(如 PRIMARY KEY 或 UNIQUE 索引)的场景:
- 必须确保
INSERT的字段包含完整唯一键,否则无法触发ON DUPLICATE KEY UPDATE - 不能用于批量更新多行且每行条件不同的场景(比如按时间范围更新状态)
- 如果唯一键是联合索引,所有列都得参与
INSERT,否则可能插入重复数据而非更新 - 注意
VALUES()函数在ON DUPLICATE KEY UPDATE中的行为:它引用的是本次INSERT的值,不是原行值
示例:
INSERT INTO user_profile (user_id, last_login, version) VALUES (123, '2024-06-15 10:30:00', 2) ON DUPLICATE KEY UPDATE last_login = VALUES(last_login), version = version + 1;
分片更新 + WHERE ... AND version = ? 控制并发写入
对高频更新字段(如计数器、状态位),避免全量扫描,把大更新拆成小批次,并用乐观锁防止覆盖:
- 加一个
version列(INT UNSIGNED),每次更新时检查旧值:UPDATE t SET cnt = cnt + 1, version = version + 1 WHERE id = 123 AND version = 5 - 执行后查
ROW_COUNT(),为 0 表示已被其他事务抢先更新,需重试或降级处理 - 配合
LIMIT 100分批更新,减少单次锁持有时间 - 确保
id和version组合上有索引,否则WHERE仍可能走全表扫描
读写分离时,如何让从库不因主库热点更新而延迟?
主库的高频 UPDATE 会生成大量 binlog,从库 SQL 线程单线程回放时极易堆积。关键不是“不让更新”,而是降低复制压力:
- 确认是否真的需要实时强一致读:能接受秒级延迟的场景,直接读从库,主库专注写
- 禁用
binlog_row_image = FULL(默认),改用MINIMAL—— 只记录被修改列和主键,大幅减小 binlog 体积 - 避免在热点行上频繁更新非业务关键字段(如
updated_at),可考虑用定时任务合并刷新 - MySQL 8.0+ 可启用
slave_parallel_workers > 0并设slave_parallel_type = LOGICAL_CLOCK,但前提是表有主键或唯一键
真正难的不是语法怎么写,而是判断哪条数据“热”到需要拆逻辑 —— 很多时候加个缓存层比死磕 SQL 更快,也更可控。


















