必须先建联合唯一索引,否则 on duplicate key update 不会触发;mysql 仅在违反 primary key 或 unique key 时执行 update 分支,若无对应索引则无法检测冲突,导致始终插入新行。

必须先建联合唯一索引,否则 ON DUPLICATE KEY UPDATE 不会触发。
为什么 ON DUPLICATE KEY UPDATE 总是插入新行,从不更新?
根本原因是 MySQL 只在违反 PRIMARY KEY 或某个 UNIQUE KEY 时才执行后续的 UPDATE 分支。如果你只靠业务逻辑“认为”三个字段组合该唯一,但表上没建对应索引,MySQL 就完全感知不到冲突。
- 检查是否真有该约束:
SHOW CREATE TABLE your_table;,确认输出里含类似UNIQUE KEY uk_token_user (token, user_id)的定义 - 常见疏漏:给错了列(比如多加了
id或created_at),或列顺序和INSERT中 VALUES 顺序不一致 - NULL 值参与联合唯一索引时行为特殊:MySQL 认为两个
NULL不相等,所以(1, NULL)和(1, NULL)不算重复 —— 若业务允许 NULL,需额外处理
如何安全添加联合唯一索引而不锁表失败?
线上表已有数据时,直接 ALTER TABLE ... ADD UNIQUE 可能因隐含重复值报错,或长时间锁表。不能跳过校验。
- 先查重复:
SELECT token, user_id, COUNT(*) FROM tb_device_user GROUP BY token, user_id HAVING COUNT(*) > 1; - 若结果非空,必须先清理(删旧、合并 count、或按业务规则保留某条)
- 加索引建议用
ALGORITHM=INPLACE, LOCK=NONE(5.6+ InnoDB 支持),例如:ALTER TABLE tb_device_user ADD UNIQUE KEY uk_token_user (token, user_id) ALGORITHM=INPLACE, LOCK=NONE; - 注意:若字段含 TEXT/BLOB 或字符集不一致,
INPLACE可能退化为拷表,务必在低峰期试运行
INSERT ... ON DUPLICATE KEY UPDATE 的字段赋值陷阱
写法看似简单,但 VALUES(column_name) 的语义容易误解 —— 它取的是本次 INSERT 语句中对应位置的值,不是原记录值。
- 正确自增计数:
INSERT INTO summary_table (col2_id, col3_id, col4_id, count) VALUES (1,2,3,1) ON DUPLICATE KEY UPDATE count = count + 1; - 错误写法(永远设为 1):
... ON DUPLICATE KEY UPDATE count = VALUES(count);—— 因为VALUES(count)就是1 - 更新时间戳应写
updated_at = NOW(),而非VALUES(updated_at),后者是 INSERT 里传的旧时间或 NULL - 若需根据新旧值做条件判断(如只在新值更晚时更新),MySQL 不支持
ON DUPLICATE内嵌IF,得改用事务 +SELECT ... FOR UPDATE
MyBatis 中批量执行的注意事项
用 <foreach></foreach> 拼多个 VALUES 时,整个语句仍只触发一次 ON DUPLICATE 分支 —— 即所有冲突都走同一条 UPDATE 规则,无法为每行定制逻辑。
- 批量插入 100 行,其中 3 行与现有记录冲突,这 3 行会统一执行你写的
UPDATE子句,其余 97 行插入 - 不要在
UPDATE部分引用VALUES()以外的变量,MyBatis 不会将其替换进 SQL - 若需不同行不同更新策略(比如有的加 count,有的覆盖 pwd),只能拆成多次单条执行,或改用存储过程
真正卡住人的地方,往往不是语法写错,而是索引没生效、NULL 值被忽略、或者误以为 VALUES() 是“当前行原值”。上线前一定用真实数据跑一遍冲突场景。











