重複數據是數據質量頭號殺手。本文系統梳理 MySQL 層面的防重約束 (PK/UK)、應用層冪等設計、統計查重 SQL、多種去重刪除方案、大表去重線上操作,構建從源頭預防到存量治理的完整體系。
CREATE TABLE person (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(20) NOT NULL,
last_name VARCHAR(20) NOT NULL,
sex CHAR(10),
PRIMARY KEY (last_name, first_name) -- 複合主鍵,隱含 NOT NULL
);
CREATE TABLE person (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
first_name VARCHAR(20) NOT NULL,
last_name VARCHAR(20) NOT NULL,
email VARCHAR(100),
UNIQUE KEY uk_email (email), -- 單列唯一
UNIQUE KEY uk_name (last_name, first_name) -- 複合唯一
);
| 語句 | 重複時行為 | 返回值 | 適用場景 |
|---|---|---|---|
INSERT | 報錯 (Duplicate entry) | 錯誤碼 1062 | 嚴格防重 |
INSERT IGNORE | 靜默忽略,不插入 | 0 rows affected | 日誌去重、冪等導入 |
REPLACE INTO | 刪除舊行 + 插入新行 | 2 rows affected | 全量覆蓋更新 |
INSERT ... ON DUPLICATE KEY UPDATE | 更新指定列 | 1/2 rows affected | 增量同步、計數器 |
-- 範例:用戶登入次數冪等遞增
INSERT INTO user_stats (user_id, login_count, last_login)
VALUES (123, 1, NOW())
ON DUPLICATE KEY UPDATE
login_count = login_count + 1,
last_login = VALUES(last_login);
SELECT COUNT(*) AS repetitions, last_name, first_name FROM person_tbl GROUP BY last_name, first_name HAVING repetitions > 1;
SELECT a.*
FROM person_tbl a
JOIN (
SELECT last_name, first_name
FROM person_tbl
GROUP BY last_name, first_name
HAVING COUNT(*) > 1
) b ON a.last_name = b.last_name AND a.first_name = b.first_name
ORDER BY a.last_name, a.first_name, a.id;
-- 僅掃描索引,不回表 SELECT last_name, first_name, COUNT(*) FROM person_tbl FORCE INDEX (uk_name) GROUP BY last_name, first_name HAVING COUNT(*) > 1;
CREATE TABLE tmp_person AS SELECT * FROM person_tbl GROUP BY last_name, first_name; -- MySQL 允許非聚合列任意取值 (ONLY_FULL_GROUP_BY 關閉時) DROP TABLE person_tbl; ALTER TABLE tmp_person RENAME TO person_tbl; -- 重建索引/約束
DELETE a FROM person_tbl a JOIN person_tbl b ON a.last_name = b.last_name AND a.first_name = b.first_name AND a.id > b.id;
WITH ranked AS (
SELECT id, ROW_NUMBER() OVER (PARTITION BY last_name, first_name ORDER BY id) AS rn
FROM person_tbl
)
DELETE FROM person_tbl WHERE id IN (SELECT id FROM ranked WHERE rn > 1);
ALTER IGNORE TABLE person_tbl ADD UNIQUE KEY uk_name (last_name, first_name);
# pt-archiver 歸檔重複行到備份表 pt-archiver --source h=localhost,D=db,t=person_tbl \ --dest h=localhost,D=db,t=person_dup_archive \ --where "id NOT IN (SELECT MIN(id) FROM person_tbl GROUP BY last_name, first_name)" \ --limit 1000 --commit-each --no-check-charset --progress 10000
-- DISTINCT (全行去重) SELECT DISTINCT last_name, first_name FROM person_tbl; -- GROUP BY (可攜帶聚合) SELECT last_name, first_name, MIN(id) AS first_id FROM person_tbl GROUP BY last_name, first_name;
CREATE TABLE idempotency (biz_key VARCHAR(64) PRIMARY KEY, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP)INSERT IGNORE 吞沒非唯一鍵錯誤REPLACE INTO 觸發自增 ID 跳躍AUTO_INCREMENT 遞增,大量 REPLACE 會耗盡 ID 空間UNIQUE (a,b) 中 a=1,b=NULL 可插入多條ALTER TABLE ADD UNIQUE 鎖表久pt-online-schema-change 或 gh-ost 線上加索引