如何判断触发重复键错误的约束键,使ON DUPLICATE KEY仅响应主键重复场景
实现仅主键冲突时触发ON DUPLICATE KEY更新的方案
MySQL原生ON DUPLICATE KEY UPDATE语句的默认逻辑是:只要是主键或任意唯一索引触发重复约束,就会执行更新逻辑,无法直接区分冲突来源,可通过以下两种方式实现需求:
方案1:先查询主键状态再分支执行(推荐,逻辑简单易维护)
在存储过程中先判断待插入的主键是否已存在,再分别走更新或插入逻辑,非主键的唯一索引冲突会在插入阶段直接抛出异常:
DELIMITER // CREATE PROCEDURE insert_or_update_member(IN p_id INT, IN p_email VARCHAR(255), IN p_其他字段 类型) BEGIN DECLARE count_id INT; -- 加行锁避免并发场景下的主键冲突 SELECT COUNT(1) INTO count_id FROM pp_member WHERE id = p_id FOR UPDATE; IF count_id > 0 THEN -- 主键已存在,执行更新 UPDATE pp_member SET member_email = p_email, 其他字段 = p_其他字段 WHERE id = p_id; ELSE -- 主键不存在,执行插入,此时若触发唯一索引冲突会直接抛出1062错误 INSERT INTO pp_member(id, member_email, 其他字段) VALUES (p_id, p_email, p_其他字段); END IF; END // DELIMITER ;
该逻辑下两种冲突的处理完全符合需求:
#1062 - Duplicate entry '4' for key 'pp_member.PRIMARY':主键冲突场景,走前置的更新逻辑不会触发该报错
#1062 - Duplicate entry 'aaa@aaa.com' for key 'pp_member.member_email_UNIQUE':唯一索引冲突场景,插入阶段直接抛出该异常
方案2:保留ON DUPLICATE KEY,主动判断冲突来源抛出异常
如果需要保留单句INSERT的写法,可以在ON DUPLICATE KEY部分增加冲突判断,非主键冲突时主动抛错:
DELIMITER // CREATE PROCEDURE insert_or_update_member(IN p_id INT, IN p_email VARCHAR(255), IN p_其他字段 类型) BEGIN DECLARE EXIT HANDLER FOR SQLSTATE '45000' BEGIN SIGNAL SQLSTATE '23000' SET MESSAGE_TEXT = 'Duplicate entry for unique index', MYSQL_ERRNO = 1062; END; INSERT INTO pp_member(id, member_email, 其他字段) VALUES (p_id, p_email, p_其他字段) ON DUPLICATE KEY UPDATE -- 仅主键匹配时继续执行更新,非主键冲突时触发自定义错误 id = IF(id = p_id, p_id, SIGNAL(SQLSTATE '45000')), member_email = VALUES(member_email), 其他字段 = VALUES(其他字段); END // DELIMITER ;
注意事项
- 方案1的
FOR UPDATE锁需要在事务内执行,避免并发写入导致的主键冲突 - 方案2需要MySQL 5.5及以上版本支持
SIGNAL语句 - 两种方案都自动覆盖所有唯一索引的冲突场景,无需针对单个唯一索引做额外适配
内容的提问来源于stack exchange,提问作者Nickal
相关产品推荐
相关产品推荐

