为什么 INSERT ... ON DUPLICATE KEY UPDATE
比 SELECT + INSERT/UPDATE
更适合高并发写入
因为前者是原子操作,避免了“读-判-写”窗口期引发的重复插入或覆盖丢失。在并发场景下,
SELECT查不到记录 → 多个事务同时走
INSERT→ 唯一键冲突;或者查到记录 → 多个事务都执行
UPDATE→ 后写覆盖前写。而
ON DUPLICATE KEY UPDATE由 MySQL 在引擎层加行锁(针对冲突的唯一索引值),天然串行化冲突路径。
实操建议:
确保冲突字段上有唯一索引(主键或UNIQUE约束),否则不触发更新逻辑 避免在
UPDATE子句中引用未在
VALUES()中提供的列,否则可能产生非预期空值 若需根据原值做计算(如计数器自增),用
col = col + 1而非
col = VALUES(col) + 1,后者会取本次插入值而非当前行值
INSERT INTO ... SELECT
在并发写入时为何容易锁表或死锁
该语句默认对源表(
SELECT部分)加一致性读锁(MVCC),但目标表(
INSERT INTO)会按实际写入顺序逐行加
INSERT intention lock和行锁。当多个事务同时执行类似语句,且涉及相同主键/唯一键范围时,容易因加锁顺序不一致导致死锁;若源表无合适索引,还可能升级为间隙锁(gap lock),阻塞其他范围写入。
实操建议:
源表SELECT条件必须命中索引,避免全表扫描触发大范围 gap lock 目标表主键或唯一键字段务必有索引,否则
INSERT过程无法准确定位冲突行,退化为更重的锁机制 考虑拆成小批量(如
LIMIT 1000+ 循环),降低单次事务持有锁的时间
如何用 innodb_lock_wait_timeout
和死锁日志定位真实瓶颈
单纯调高
innodb_lock_wait_timeout(默认 50 秒)只是掩盖问题,真正要解决的是锁等待源头。MySQL 的
SHOW ENGINE INNODB STATUS输出中,
LATEST DETECTED DEADLOCK段落会明确列出两个事务各自持有的锁、等待的锁、SQL 语句及加锁的索引记录 —— 这比应用层报错信息更精准。
实操建议:
在业务 SQL 中显式指定锁粒度:需要强一致性时用SELECT ... FOR UPDATE,仅防幻读可用
SELECT ... LOCK IN SHARE MODE检查事务中是否有长耗时操作(如远程调用、大循环),它们会拉长锁持有时间,放大竞争概率 确认是否误用了
REPEATABLE READ隔离级别下的间隙锁:若业务允许,可降级为
READ COMMITTED(需关闭
binlog_format = 'STATEMENT')
批量写入时 INSERT
语句合并与事务大小的平衡点
单条
INSERT开销大(网络往返 + 日志刷盘),但事务过大(如 10 万行)会导致 undo log 膨胀、锁持有时间过长、主从延迟加剧。经验值是每事务 1k–5k 行,具体取决于单行数据大小和磁盘 I/O 能力。
实操建议:
用INSERT INTO t VALUES (...), (...), (...)合并多行,而非多次单行
INSERT禁用自动提交(
SET autocommit = 0),手动控制
COMMIT时机 避免在事务中混用 DDL(如
ALTER TABLE),它会隐式提交当前事务并锁全表
INSERT INTO orders (user_id, amount, status) VALUES (1001, 99.9, 'paid'), (1002, 129.5, 'paid'), (1003, 59.0, 'pending');
真正难处理的不是锁本身,而是不同业务逻辑对“一致性”的定义差异——比如库存扣减要求绝对精确,而日志写入允许短暂重复。把锁策略和业务语义对齐,比调参重要得多。
