表 `users(id PK, name)` 中 `name` 有重複,需保留每組 `name` 的**最小 `id`**,刪除其餘重複行。
MySQL 不允許 `DELETE` 目標表同時出現在子查詢中 (報錯 1093)。**解決:用衍生表包裝子查詢**。
DELETE FROM users
WHERE id IN (
SELECT * FROM ( -- 關鍵:衍生表物化,繞過 1093 錯誤
SELECT MIN(id)
FROM users
GROUP BY name
HAVING COUNT(name) > 1
) AS t
);
> ⚠️ `DELETE FROM users` **不能使用別名**,否則語法錯誤。
DELETE a
FROM users AS a
INNER JOIN (
SELECT MIN(id) AS id, name
FROM users
GROUP BY name
HAVING COUNT(name) > 1
) AS b ON a.name = b.name AND a.id <> b.id;
-- 1. 創建臨時表存儲去重後的數據
CREATE TABLE tmp_users AS
SELECT MIN(id) AS id, name FROM users GROUP BY name;
-- 2. 清空原表 (TRUNCATE 瞬間完成,不產生 Binlog/Undo Log)
TRUNCATE TABLE users;
-- 3. 寫回去重數據
INSERT INTO users SELECT * FROM tmp_users;
-- 4. 清理
DROP TABLE tmp_users;
優勢:避免大事務長鎖表,`TRUNCATE` + `INSERT` 速度極快,適合千萬級大表。劣勢:需短暫鎖表 (TRUNCATE/INSERT),主鍵 ID 會重排 (若業務依賴 ID 不變則不可用)。
| 維度 | 方法一:衍生表子查詢 | 方法二:連接刪除 | 方法三:臨時表重建 |
|---|---|---|---|
| 適用數據量 | 中小表 (<100萬) | 中大表 | 超大表 (>500萬) / 高頻去重 |
| 鎖表時間 | 較長 (全表掃描+刪除) | 中等 | 極短 (TRUNCATE 瞬時) |
| ID 保持不變 | ✅ 保留原最小 ID | ✅ 保留原最小 ID | ❌ ID 重排 (重新插入) |
| 語法兼容性 | MySQL 專用 (衍生表) | 標準 SQL (通用) | 通用 |
| 推薦指數 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ (大表首選) |
-- 保留最大 ID (最新插入)
-- 方法二修改:
DELETE a FROM users a
INNER JOIN (
SELECT MAX(id) AS id, name FROM users GROUP BY name HAVING COUNT(*) > 1
) b ON a.name = b.name AND a.id <> b.id;
-- 保留最新時間 (假設有 create_time)
DELETE a FROM users a
INNER JOIN (
SELECT name, MAX(create_time) AS max_time FROM users GROUP BY name HAVING COUNT(*) > 1
) b ON a.name = b.name AND a.create_time < b.max_time;
-- 去重完成後,添加唯一約束
ALTER TABLE users ADD UNIQUE KEY uk_name (name);
-- 或複合唯一
ALTER TABLE users ADD UNIQUE KEY uk_name_phone (name, phone);