表 test(id INT PRIMARY KEY, name VARCHAR(20)),name 有重複值,需**保留每組第一條(最小 id)**,刪除其餘重複行。
-- 1. 建臨時表存每組最小 id
CREATE TEMPORARY TABLE tmp_ids AS
SELECT MIN(id) AS id, name FROM test GROUP BY name HAVING COUNT(*) > 1;
-- 2. 刪除非最小 id 的重複行
DELETE t FROM test t
INNER JOIN tmp_ids tmp ON t.name = tmp.name AND t.id > tmp.id;
-- 3. 清理
DROP TEMPORARY TABLE tmp_ids;
DELETE t1 FROM test t1
INNER JOIN test t2
ON t1.name = t2.name AND t1.id > t2.id;
-- 1. 建新表保留去重資料
CREATE TABLE test_dedup AS
SELECT * FROM test WHERE id IN (
SELECT MIN(id) FROM test GROUP BY name
);
-- 2. 重建索引/約束
ALTER TABLE test_dedup ADD PRIMARY KEY (id);
-- 其他索引...
-- 3. 原子切換
RENAME TABLE test TO test_old, test_dedup TO test;
-- 4. 驗證無誤後刪舊表
DROP TABLE test_old;
WITH ranked AS (
SELECT id, name,
ROW_NUMBER() OVER (PARTITION BY name ORDER BY id) AS rn
FROM test
)
DELETE FROM test
WHERE id IN (SELECT id FROM ranked WHERE rn > 1);
ROW_NUMBER() 為每組按 id 排序打標,rn=1 保留。-- 1. 忽略錯誤建立唯一索引,自動刪除重複行(保留第一條)
ALTER IGNORE TABLE test ADD UNIQUE INDEX uniq_name (name);
-- ⚠️ MySQL 5.7+ 移除 IGNORE 子句,此法失效
MySQL 5.6 及以下有效,新版不可用。保留作歷史參考。
| 資料量 | 重複率 | 推薦方案 | 備註 |
|---|---|---|---|
| < 10 萬 | 任意 | 方案一或二 | 單機秒級完成 |
| 10 萬 ~ 1000 萬 | 低 (<10%) | 方案一(臨時表) | 配合索引較快 |
| 10 萬 ~ 1000 萬 | 高 (>10%) | 方案三(新表換名) | 避免長鎖、碎片整理 |
| > 1000 萬 | 任意 | 方案三 + 分批/pt-archiver | 配合線上變更工具 |
| MySQL 8.0 環境 | 任意 | 方案四(CTE+窗口) | 語義清晰、優化器友好 |
ALTER TABLE test ADD UNIQUE INDEX uniq_name (name); 寫入時拋錯,應用捕獲處理。INSERT IGNORE INTO test (name) VALUES ('duplicate'); -- 靜默忽略
INSERT INTO test (name) VALUES ('dup') ON DUPLICATE KEY UPDATE name=VALUES(name); -- 更新或忽略
刪除重複資料無銀彈。**小表用自連接 DELETE,大表用新表換名(RENAME),MySQL 8.0 首選 CTE+窗口函數**。最重要的是:在源頭加唯一索引約束,從架構層面杜絕重複產生。