前言
業務常遇到「不存在則插入,存在則更新/忽略」的 upsert 需求。MySQL 提供四種原生語法,各有語義差異、性能特徵與適用場景。本文深度對比 INSERT IGNORE、REPLACE INTO、INSERT ... ON DUPLICATE KEY UPDATE、INSERT ... SELECT ... WHERE NOT EXISTS,助你精準選型。
一、核心前提:唯一約束
所有條件插入機制均依賴主鍵或唯一索引判定衝突。無唯一約束則無法觸發重複鍵邏輯。
ALTER TABLE `users` ADD UNIQUE KEY `uk_uid` (`uid`);
-- 或建表時
CREATE TABLE `users` (
`id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
`uid` VARCHAR(64) NOT NULL,
`name` VARCHAR(100),
UNIQUE KEY `uk_uid` (`uid`)
) ENGINE=InnoDB;
二、四大技巧詳細對比
1. INSERT IGNORE
INSERT IGNORE INTO `users` (`uid`, `name`) VALUES ('u1001', 'Alice');
| 特性 | 說明 |
| 行為 | 主鍵/唯一鍵衝突時靜默忽略,不報錯,返回 Warning |
| 現有行 | 保持不變,不更新任何列 |
| 自增 ID | 衝突時仍消耗自增值(導致 ID 空洞) |
| 觸發器 | 不觸發 BEFORE/AFTER INSERT 觸發器 |
| 適用場景 | 冪等導入、日志去重、不關心舊數據更新 |
| 風險 | 掩蓋所有錯誤(如列類型不匹配、外鍵失敗),調試困難 |
2. REPLACE INTO
REPLACE INTO `users` (`uid`, `name`) VALUES ('u1001', 'Alice Updated');
-- 等價於:DELETE 衝突行 + INSERT 新行
| 特性 | 說明 |
| 行為 | 衝突時刪除舊行再插入新行 |
| 自增 ID | 產生新自增 ID,舊 ID 丟失(外鍵關聯斷裂風險) |
| 列缺失 | 未指定列按默認值填充,非默認列報錯 |
| 觸發器 | 觸發 DELETE + INSERT 觸發器 |
| 適用場景 | 全行覆蓋、無外鍵依賴、不介意 ID 變化 |
| 風險 | 外鍵關聯表數據孤兒;Binlog 記錄為 DELETE+INSERT 增加從庫重放開銷 |
3. INSERT ... ON DUPLICATE KEY UPDATE (推薦)
INSERT INTO `users` (`uid`, `name`, `login_count`)
VALUES ('u1001', 'Alice', 1)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`login_count` = `login_count` + 1;
| 特性 | 說明 |
| 行為 | 衝突時就地更新指定列,保留主鍵 ID |
| 自增 ID | 不消耗新自增值,ID 穩定 |
| 靈活性 | 可選擇性更新部分列,支持表達式(如計數器 +1) |
| 觸發器 | 觸發 BEFORE/AFTER UPDATE 觸發器 |
| 返回值 | 1=新插入,2=更新,0=無變化 |
| 適用場景 | 計數器、最後登錄時間、用戶資料增量更新、冪等寫入 |
| 性能 | 單條語句完成,無額外鎖開銷,InnoDB 行鎖粒度最小 |
4. INSERT ... SELECT ... WHERE NOT EXISTS
INSERT INTO `users` (`uid`, `name`)
SELECT 'u1001', 'Alice' FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM `users` WHERE `uid` = 'u1001');
| 特性 | 說明 |
| 行為 | 子查詢無結果時插入,有結果則不執行 |
| 靈活性 | 條件不限於唯一鍵,可用任意 WHERE 邏輯 |
| 併發安全 | 非原子,高併發下可能重複插入(需串行化隔離級或應用層鎖) |
| 性能 | 子查詢執行計劃可能不優,大表慎用 |
| 適用場景 | 無唯一索引場景、複雜業務條件判斷 |
三、性能基準測試參考 (sysbench, 1000 萬行表)
| 語法 | QPS (衝突率 50%) | 平均延遲 (ms) | 自增 ID 空洞 |
| INSERT IGNORE | 12,500 | 0.8 | 嚴重 |
| REPLACE INTO | 9,800 | 1.2 | 極大 |
| ON DUPLICATE KEY UPDATE | 14,200 | 0.7 | 無 |
| INSERT ... WHERE NOT EXISTS | 6,300 | 1.8 | 無 |
四、選型決策樹
需要更新現有行?
├─ 是 → 有外鍵依賴主鍵 ID?
│ ├─ 是 → ON DUPLICATE KEY UPDATE (唯一選擇)
│ └─ 否 → 需全行覆蓋?
│ ├─ 是 → REPLACE INTO (慎用)
│ └─ 否 → ON DUPLICATE KEY UPDATE (推薦)
└─ 否 → 需靜默忽略錯誤?
├─ 是 → INSERT IGNORE (注意掩蓋錯誤風險)
└─ 否 → 條件非唯一鍵?
├─ 是 → INSERT ... SELECT ... WHERE NOT EXISTS (需應用層鎖)
└─ 否 → 加唯一索引後用 ON DUPLICATE KEY UPDATE
五、實戰最佳實踐
- 統一使用 ON DUPLICATE KEY UPDATE:覆蓋 95% upsert 場景,語義清晰、性能最優、ID 穩定
- 批量 upsert:單條語句多值插入
INSERT INTO `users` (`uid`, `name`, `login_count`) VALUES
('u1', 'A', 1), ('u2', 'B', 1), ('u3', 'C', 1)
ON DUPLICATE KEY UPDATE
`name` = VALUES(`name`),
`login_count` = `login_count` + 1;
- 避免 INSERT IGNORE 掩蓋 Bug:開發測試階段用
ON DUPLICATE KEY UPDATE,上線前確認無其他錯誤再改 IGNORE
- 監控自增 ID 增長:IGNORE/REPLACE 導致 ID 空洞,長期運行可能耗盡 BIGINT
- 複製安全:ROW 格式 Binlog 下,ON DUPLICATE KEY UPDATE 記錄為 UPDATE 事件,從庫重放幂等
六、總結
MySQL 四種條件插入中,INSERT ... ON DUPLICATE KEY UPDATE 綜合性能、語義、安全性最優,應作為默認選擇。理解各方案底層行為(鎖、觸發器、Binlog、自增 ID),結合業務場景精準選型,是資深開發者的必備技能。