同一 SQL 中對同一表進行子查詢並更新/刪除:
DELETE FROM tbl
WHERE id IN (
SELECT MAX(id) FROM tbl a WHERE EXISTS (
SELECT 1 FROM tbl b WHERE a.tac = b.tac
GROUP BY tac HAVING COUNT(1) > 1
)
GROUP BY tac
);
報錯:ERROR 1093 (HY000): You can't specify target table 'tbl' for update in FROM clause
MySQL 為防止子查詢與外層 DML 交互導致數據不一致,禁止在同一語句中讀取並修改同一表。Oracle/PostgreSQL/SQL Server 通過 MVCC 一致性視圖允許此操作,MySQL 選擇了保守策略。
DELETE FROM tbl
WHERE id IN (
SELECT id FROM (
SELECT MAX(id) AS id FROM tbl a WHERE EXISTS (
SELECT 1 FROM tbl b WHERE a.tac = b.tac
GROUP BY tac HAVING COUNT(1) > 1
)
GROUP BY tac
) AS tmp
);
關鍵:最外層再套一層 SELECT id FROM (...) AS tmp,將子查詢物化為臨時表,打破「同表讀寫」直接關聯。
DELETE t1
FROM tbl t1
JOIN (
SELECT MAX(id) AS id FROM tbl a WHERE EXISTS (
SELECT 1 FROM tbl b WHERE a.tac = b.tac
GROUP BY tac HAVING COUNT(1) > 1
)
GROUP BY tac
) t2 ON t1.id = t2.id;
優點:單語句完成,無臨時表開銷,利用 JOIN 語義天然規避限制。
SELECT MAX(id) INTO @ids FROM tbl a WHERE EXISTS (
SELECT 1 FROM tbl b WHERE a.tac = b.tac
GROUP BY tac HAVING COUNT(1) > 1
) GROUP BY tac;
DELETE FROM tbl WHERE id IN (@ids);
缺點:需拆分為兩條語句,事務一致性需應用層保證。
| 方案 | 適用版本 | 效能 | 可讀性 | 推薦指數 |
|---|---|---|---|---|
| 派生表包裹 | 5.0+ | 中 (可能物化臨時表) | 高 | ⭐⭐⭐⭐ |
| 多表 DELETE JOIN | 4.0+ | 高 (單掃描) | 中 | ⭐⭐⭐⭐⭐ |
| 用戶變量 | 所有 | 低 (多次交互) | 低 | ⭐⭐ |
-- 錯誤
UPDATE tbl SET status=1 WHERE id IN (SELECT id FROM tbl WHERE ...);
-- 正確 (派生表)
UPDATE tbl SET status=1 WHERE id IN (SELECT id FROM (SELECT id FROM tbl WHERE ...) AS tmp);
-- 正確 (多表 UPDATE)
UPDATE tbl t1 JOIN (SELECT id FROM tbl WHERE ...) t2 ON t1.id=t2.id SET t1.status=1;
MySQL 1093 錯誤是「保守一致性」的產物。掌握「派生表包裹」與「多表 JOIN 刪除/更新」兩大核心模式,覆蓋 99% 場景。新版本 (8.0+) 優化器對派生表合併更智能,可放心使用。