联合主键仅提供唯一性约束,不能防止dml并发冲突;真正避免1062错误的是配合insert ... on duplicate key update等原子操作,否则select then insert必因竞态触发冲突。

联合主键本身不能防止DML并发冲突,它只提供唯一性约束;真正起作用的是配合INSERT ... ON DUPLICATE KEY UPDATE或UPDATE ... WHERE条件的原子操作。
为什么联合主键 + INSERT 会报 1062 错误
很多人以为建了联合主键就能“自动防并发”,结果在高并发下仍频繁遇到 ERROR 1062: Duplicate entry。这是因为:
- 联合主键只是声明“这组列值必须全局唯一”,不带任何锁或重试逻辑
- 应用层若用
SELECT判断存在后再INSERT,两个请求几乎同时查到“不存在”,接着都执行INSERT,第二个必然触发 1062 - 即使加了
SELECT ... FOR UPDATE,若 WHERE 条件没走索引,InnoDB 可能升级为间隙锁甚至表锁,反而放大阻塞
正确用法:联合主键必须搭配原子写入语句
只有把“判断”和“写入”压进一条 SQL,才能靠 InnoDB 层保障原子性。前提是联合主键(或 UNIQUE 约束)已存在:
-
INSERT INTO t (a, b, c) VALUES (1,2,100) ON DUPLICATE KEY UPDATE c = c + 10;—— 冲突时更新,不报错 -
INSERT IGNORE INTO t (a,b,c) VALUES (1,2,100);—— 静默忽略,但也会吞掉字段超长、NULL 插 NOT NULL 等非唯一类错误 -
UPDATE t SET c = c + 10 WHERE a = 1 AND b = 2 AND c > 0;—— 利用 WHERE 条件做业务校验,影响行为 0 或 1,无异常
注意:ON DUPLICATE KEY UPDATE 中的 VALUES(c) 指本次 INSERT 的值,不是原记录值;别误写成 c = c + 10(那是读-改-写,非原子)。
建联合主键时最容易踩的坑
联合主键不是“多加一个主键”,而是对主键定义方式的根本理解偏差:
- MySQL 不允许存在两个主键;已有
id INT PRIMARY KEY AUTO_INCREMENT,再想用(user_id, action_type)做业务唯一性,应建UNIQUE KEY (user_id, action_type),而非“加第二个主键” - 建表时联合主键必须写在最后:
PRIMARY KEY (user_id, action_type),且两列都得是NOT NULL,否则建表失败 - 已有单列主键要改成联合主键,必须先
ALTER TABLE t DROP PRIMARY KEY,再ADD PRIMARY KEY (a,b);跳过删除步骤会报ERROR 1068: Multiple primary key defined - 联合主键的列顺序影响索引效率:查询只含后一列(如只查
action_type)无法使用该索引,遵循最左前缀原则
比联合主键更关键的是事务边界控制
哪怕用了 ON DUPLICATE KEY UPDATE,如果事务里混了 HTTP 调用、Redis 读写或日志打印,锁持有时间就会从几毫秒拉长到几百毫秒。这时:
- 其他事务等待超时,报
Lock wait timeout exceeded - 死锁概率陡增,尤其在多表 JOIN 更新时,优化器加锁顺序不可控
- 真正拖垮系统的往往不是慢 SQL,而是那个在
BEGIN...COMMIT里等外部服务响应的 800ms
所以务必把所有非 DB 操作移出事务块——这点比选什么主键类型重要得多。











