資料唯一性保障是資料庫設計的核心課題。MySQL 提供多種防重機制,本文深度解析 INSERT ... WHERE NOT EXISTS 模式、INSERT IGNORE、ON DUPLICATE KEY UPDATE 等原生方案,並探討應用層協同策略。
適用於跨表資料遷移、批量匯入去重等場景,符合 ANSI SQL 標準,可移植性最強。
INSERT INTO `clients` (`client_id`, `client_name`, `client_type`)
SELECT `supplier_id`, `supplier_name`, 'advertising'
FROM `suppliers`
WHERE NOT EXISTS (
SELECT 1 FROM `clients`
WHERE `clients`.`client_id` = `suppliers`.`supplier_id`
);
INSERT INTO `clients` (`client_id`, `client_name`, `client_type`)
SELECT 10345, 'IBM', 'advertising'
FROM `dual`
WHERE NOT EXISTS (
SELECT 1 FROM `clients` WHERE `client_id` = 10345
);
關鍵點:使用虛擬表 dual 使 SELECT 可直接帶入常值,無需實體來源表。
需表擁有 主鍵 或 唯一索引 才能觸發衝突檢測。
INSERT IGNORE INTO `clients` (`client_id`, `client_name`)
VALUES (10345, 'IBM');
重複鍵衝突時靜默忽略,不報錯、不插入,affected_rows 為 0。
INSERT INTO `clients` (`client_id`, `client_name`, `visit_count`)
VALUES (10345, 'IBM', 1)
ON DUPLICATE KEY UPDATE
`visit_count` = `visit_count` + 1,
`last_visit` = NOW();
此為「不存在則插入,存在則更新」的原子操作,極適合計數器、狀態同步等高併發場景。
常見但低效的 PHP 寫法:
$title = 'www.example.com';
$sql = "SELECT * FROM `tablename` WHERE `title`='$title'";
$query = mysql_query($sql);
if (mysql_num_rows($query)) {
exit('重複資料');
} else {
mysql_query("INSERT INTO `tablename` VALUES('$title')");
exit('儲存成功');
}
$stmt = $pdo->prepare("INSERT INTO `tablename` (`title`) VALUES (?)");
try {
$pdo->beginTransaction();
$stmt->execute([$title]);
$pdo->commit();
echo '儲存成功';
} catch (PDOException $e) {
if ($e->getCode() == 23000) { // Integrity constraint violation
echo '重複資料';
} else {
throw $e;
}
$pdo->rollBack();
}
| 場景 | 推薦方案 | 核心優勢 |
|---|---|---|
| 跨表批量遷移去重 | INSERT ... WHERE NOT EXISTS | 標準 SQL、集合操作、效能佳 |
| 單筆業務寫入、需更新既有資料 | ON DUPLICATE KEY UPDATE | 原子化 Upsert、避免競態條件 |
| 單筆寫入、重複即拒絕 | INSERT IGNORE | 語法簡潔、無錯誤拋出 |
| 高併發計數器、統計聚合 | ON DUPLICATE KEY UPDATE + 唯一索引 | 無鎖樂觀並發、效能最優 |
UNIQUE INDEX 或 PRIMARY KEYaffected_rows、LAST_INSERT_ID() 判斷操作結果ON DUPLICATE KEY UPDATE 可能引發間隙鎖衝突建立「資料庫守門人」思維,將資料完整性約束下沉至儲存層,配合應用層優雅異常處理,才能構建健壯的資料寫入體系。