不能用 REPLACE INTO 做高并发同步,因其本质是「删+插」,导致自增 ID 跳变、误删关联数据、锁等待甚至死锁,且不满足原子性 upsert 语义。

为什么不能用 REPLACE INTO 做高并发同步
REPLACE INTO 本质是「删+插」,会触发 DELETE + INSERT 两轮操作,自增 ID 必然跳变,还可能误删关联数据(比如外键级联动作),更严重的是:在高并发下极易引发锁等待甚至死锁。它不满足原子性 upsert 语义,只是个危险的替代品。
ON DUPLICATE KEY UPDATE 的触发条件必须明确
这个语法只响应主键(PRIMARY KEY)或唯一索引(UNIQUE INDEX)冲突,普通索引无效。如果你靠 email 做存在判断,就必须确保该字段有 UNIQUE 约束,否则语句静默执行插入,根本不会进 ON DUPLICATE KEY UPDATE 分支。
- 建表时漏加
UNIQUE是最常见错误,比如ALTER TABLE users ADD UNIQUE (email); - 复合唯一索引也支持,例如
UNIQUE (tenant_id, user_id),但ON DUPLICATE KEY UPDATE无法指定匹配哪个索引,只要任一唯一约束冲突就触发 - 如果表有多个唯一键,冲突时更新行为不可控——MySQL 会选择第一个匹配的唯一键来处理,不保证顺序
VALUES() 函数和字段引用的区别很关键
VALUES(column_name) 返回的是本次 INSERT 子句中对应位置的原始值,不是计算后的新值。比如想让登录次数 +1,写成 login_count = VALUES(login_count) + 1 是错的——因为 VALUES(login_count) 就是你要插入的那个字面值,不是数据库里原来的值。
- 正确写法是:
login_count = login_count + 1(直接引用原字段) - 若需保留插入值(如覆盖式更新),才用
name = VALUES(name) - 复杂逻辑(如取当前时间、拼接字符串)建议提前在应用层算好,避免 SQL 内嵌函数增加解析开销
批量 Upsert 时 MyBatis 动态 SQL 的坑
MyBatis 的 <foreach> 批量构造 INSERT ... VALUES (...),(...),... 时,ON DUPLICATE KEY UPDATE 只能写一次,且所有 VALUES() 引用都指向各自对应行的插入值——这点容易误解。
- 不要在
UPDATE子句里写#{item.xxx},那是 MyBatis 的参数绑定,而VALUES(col)是 MySQL 内置函数 - 更新时间统一用
NOW()没问题,但别用#{now}这类传参,会导致每行更新时间不同 - 如果某字段只想在插入时设置、更新时不碰它,就别写进
ON DUPLICATE KEY UPDATE列表里——它不会被修改
email 当唯一键,但邮箱可变;或者用 phone 做冲突判定,却没考虑区号格式差异——这些都会让 ON DUPLICATE KEY UPDATE 表面跑通,实则数据错乱。


















