前言
朋友们,日常开发中处理 MySQL 数据插入时,是不是总被主键 / 唯一键冲突的问题困扰?用 “先查后插” 看似稳妥,高并发下却容易出现 Duplicate entry、性能拉胯;想直接插入又怕冲突,不知道该怎么写?
这篇文章把 MySQL 处理插入冲突的 4 种核心方案(先查后插、INSERT IGNORE、ON DUPLICATE KEY UPDATE、REPLACE INTO)拆透,做了详细地实测对比,最后给出明确的选型建议 ,分享给大家!
INSERT IGNORE
INSERT IGNORE INTO user (id, name, phone)VALUES (1, 张三, 13800138000);主键/唯一键冲突时,忽略,返回 0 rows affected优点:一行搞定,无锁竞争缺点:冲突时不会更新其他字段,有错误也会忽略(掉所有错误)。INSERT ... ON DUPLICATE KEY UPDATE
INSERT INTO user (id, name, phone, update_time)VALUES (1, 张三, 13800138000, NOW())ON DUPLICATE KEY UPDATEupdate_time = NOW(); -- 冲突时只更新时间,其他不变主键或唯一键 冲突时执行 UPDATE 子句,可控制更新哪些字段。更新了:返回 2 rows affected, 纯插入:返回 1 rows affected 。注:更新了为什么返回 2 rows affected?MySQL 5.0 之前真正实现 "删旧行+插新行",代码复用 REPLACE INTO 逻辑;MySQL 5.0 之后优化为 原地更新,但为避免影响业务计数仍保留 2。
REPLACE INTO
REPLACE INTO user (id, name, phone)VALUES (1, 张三, 13800138001);冲突时先删除旧行,再插入新行副作用:自增 ID 会变化,触发器会执行 DELETE+INSERT无冲突,纯插入:返回 1 rows affected ; 冲突了:先 DELETE 旧行 再 INSERT 新行,返回 1 rows affected ,这是 真正的删除 + 插入。先查后插
在实际业务中很多场景(特别是同步数据)都是:先查询判断数据是否存在,如果存在则不做任何处理,如果不存在则插入。这是一种不是非常好发的方案,与 insert ignore和INSERT ... ON DUPLICATE KEY UPDATE对比如下:
指标
先查后插
INSERT IGNORE
INSERT ... ON DUPLICATE KEY UPDATE
IO
2次SQL,2次IO
1次SQL,1次IO
1次SQL,1次IO
操作耗时
2~4 ms
1~2 ms
1.5~2.5 ms
Duplicate entry
有竞态窗口,并发高了会出现
天然原子性,不会出现
天然原子性,不会出现
数据库连接占用
翻倍(事务持有更长)
减半
略多于 IGNORE(多 UPDATE 阶段)
代码复杂度
需要 try-catch + 重试
一行搞定
中(需处理返回值)
总结
先查询再插入/修改存在各种问题,建议:
冲突时 什么都不做 :用 INSERT IGNORE。
冲突时 做UPDATE :用 INSERT ... ON DUPLICATE KEY UPDATE。
PostGreSQL
PostGreSQL用 INSERT ... ON CONFLICT)实现相同功能,而且更灵活。如下:
// 冲突时忽略INSERT INTO user (id, name, phone)VALUES (1, 张三, 13800138000)ON CONFLICT (id) DO NOTHING; // 冲突时修改INSERT INTO user (id, name, phone)VALUES (1, 张三, 13800138000)ON CONFLICT (phone) -- 指定唯一索引列--ON CONFLICT ON CONSTRAINT uk_phone -- 指定约束名DO UPDATE SET update_time = NOW();PostGreSQL和MySQL思想一样,不建议使用 查询再插入/修改 ,建议直接使用 INSERT ... ON CONFLICT 。
评论 (0)