[英]DELETE records which do not have a match in another table
有兩個表由id鏈接:
item_tbl (id)
link_tbl (item_id)
有一些記錄item_tbl
沒有匹配的行link_tbl
。 一個可以計算其金額的選擇將是:
SELECT COUNT(*)
FROM link_tbl lnk LEFT JOIN item_tbl itm ON lnk.item_id=itm.id
WHERE itm.id IS NULL
我想從link_tbl
刪除那些孤兒記錄(那些在另一個表中沒有匹配的記錄),但我能想到的唯一方法是:
DELETE FROM link_tbl lnk
WHERE lnk.item_id NOT IN (SELECT itm.id FROM item_tbl itm)
有
262086253個記錄link_tbl
item_tbl中的item_tbl
在16844347個孤立記錄link_tbl
。
服務器有4GB RAM和8核CPU。
EXPLAIN DELETE FROM link_tbl lnk
WHERE lnk.item_id NOT IN (SELECT itm.id FROM item_tbl itm)
返回:
Delete on link lnk (cost=0.00..11395249378057.98 rows=131045918 width=6)
-> Seq Scan on link lnk (cost=0.00..11395249378057.98 rows=131045918 width=6)
Filter: (NOT (SubPlan 1))
SubPlan 1
-> Materialize (cost=0.00..79298.10 rows=3063207 width=4)
-> Seq Scan on item itm (cost=0.00..52016.07 rows=3063207 width=4)
問題是:
link_tbl
刪除孤立記錄? 上面的解釋有多准確,或刪除這些記錄需要多長時間?
解析度:
謝謝大家的建議,非常有幫助。 我終於使用了Erwin Brandstetter建議的刪除https://stackoverflow.com/a/15959896/1331340,但我稍微調整了一下:
DELETE FROM link_tbl lnk
WHERE lnk.item_id BETWEEN 0 AND 10000
AND lnk.item_id NOT IN (SELECT itm.id FROM item itm
WHERE itm.id BETWEEN 0 AND 10000)
我比較了NOT IN和NOT EXISTS的結果,輸出在下面,雖然我使用COUNT而不是DELETE,我認為應該是相同的(我的意思是為了相對比較):
EXPLAIN ANALYZE SELECT COUNT(*)
FROM link_tbl lnk
WHERE lnk.item_id BETWEEN 0 AND 20000
AND lnk.item_id NOT IN (SELECT itm.id
FROM item_tbl itm
WHERE itm.id BETWEEN 0 AND 20000);
QUERY PLAN
Aggregate (cost=6002667.56..6002667.57 rows=1 width=0) (actual time=226817.086..226817.088 rows=1 loops=1)
-> Seq Scan on link_tbl lnk (cost=1592.50..5747898.65 rows=101907564 width=0) (actual time=206.029..225289.570 rows=566625 loops=1)
Filter: ((item_id >= 0) AND (item_id <= 20000) AND (NOT (hashed SubPlan 1)))
SubPlan 1
-> Index Scan using item_tbl_pkey on item_tbl itm (cost=0.00..1501.95 rows=36221 width=4) (actual time=0.056..99.266 rows=17560 loops=1)
Index Cond: ((id >= 0) AND (id <= 20000))
Total runtime: 226817.211 ms
EXPLAIN ANALYZE SELECT COUNT(*)
FROM link_tbl lnk WHERE lnk.item_id>0 AND lnk.item_id<20000
AND NOT EXISTS (SELECT 1 FROM item_tbl itm WHERE itm.id=lnk.item_id);
QUERY PLAN
Aggregate (cost=8835772.00..8835772.01 rows=1 width=0)
(actual time=1209235.133..1209235.135 rows=1 loops=1)
-> Hash Anti Join (cost=102272.16..8835771.99 rows=1 width=0)
(actual time=19315.170..1207900.612 rows=566534 loops=1)
Hash Cond: (lnk.item_id = itm.id)
-> Seq Scan on link_tbl lnk (cost=0.00..5091076.55 rows=203815128 width=4) (actual time=0.016..599147.604 rows=200301872 loops=1)
Filter: ((item_id > 0) AND (item_id < 20000))
-> Hash (cost=52016.07..52016.07 rows=3063207 width=4) (actual time=19313.976..19313.976 rows=3033811 loops=1)
Buckets: 131072 Batches: 4 Memory Usage: 26672kB
-> Seq Scan on item_tbl itm (cost=0.00..52016.07 rows=3063207 width=4) (actual time=0.013..9274.158 rows=3033811 loops=1)
Total runtime: 1209260.228 ms
NOT EXISTS慢了5倍。
只要我擔心,實際刪除的數據就沒有了,我能夠分5批刪除它(10000-20000,20000-100000,100000-200000,200000-1000000和1000000-1755441)。 起初我發現了max item_id,我只需要經過一半的表。
當我嘗試NOT IN或EXISTS沒有范圍(選擇計數)它甚至沒有完成,我讓它在夜間運行,它仍然在早上運行。
我想我正在尋找使用來自wildplasser的回答https://stackoverflow.com/a/15988033/1331340的 DELETE但它來得太晚了。
DELETE FROM one o
USING (
SELECT o2.id
FROM one o2
LEFT JOIN two t ON t.one_id = o2.id
WHERE t.one_id IS NULL
) sq
WHERE sq.id = o.id
;
我使用{work_mem,effective_cache_size,random_page_cost}的不同設置對四個典型查詢進行基准測試,這些設置對所選計划的影響最大。 我首先使用我的默認設置“運行”來加熱緩存。 注意:測試集足夠小,可以在緩存中顯示所有需要的頁面。
測試集
SET search_path=tmp;
/************************/
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;
CREATE TABLE one
( id SERIAL NOT NULL PRIMARY KEY
, payload varchar
);
CREATE TABLE two
( id SERIAL NOT NULL PRIMARY KEY
, one_id INTEGER REFERENCES one
, payload varchar
);
INSERT INTO one (payload) SELECT 'Text_' || gs::text FROM generate_series(1,30000) gs;
INSERT INTO two (payload) SELECT 'Text_' || gs::text FROM generate_series(1,30000) gs;
UPDATE two t
SET one_id = o.id
FROM one o
WHERE o.id = t.id
AND random() < 0.1;
INSERT INTO two (one_id,payload) SELECT one_id,payload FROM two;
INSERT INTO two (one_id,payload) SELECT one_id,payload FROM two;
INSERT INTO two (one_id,payload) SELECT one_id,payload FROM two;
VACUUM ANALYZE one;
VACUUM ANALYZE two;
/***************/
查詢:
\echo NOT EXISTS()
EXPLAIN ANALYZE
DELETE FROM one o
WHERE NOT EXISTS ( SELECT * FROM two t
WHERE t.one_id = o.id
);
\echo NOT IN()
EXPLAIN ANALYZE
DELETE FROM one o
WHERE o.id NOT IN ( SELECT one_id FROM two t)
;
\echo USING (subquery self LEFT JOIN two where NULL)
EXPLAIN ANALYZE
DELETE FROM one o
USING (
SELECT o2.id
FROM one o2
LEFT JOIN two t ON t.one_id = o2.id
WHERE t.one_id IS NULL
) sq
WHERE sq.id = o.id
;
\echo USING (subquery self WHERE NOT EXISTS(two)))
EXPLAIN ANALYZE
DELETE FROM one o
USING (
SELECT o2.id
FROM one o2
WHERE NOT EXISTS ( SELECT *
FROM two t WHERE t.one_id = o2.id
)
) sq
WHERE sq.id = o.id
;
結果(總結)
NOT EXISTS() NOT IN() USING(LEFT JOIN NULL) USING(NOT EXISTS)
1) rpc=4.0.csz=1M wmm=64 80.358 14389.026 77.620 72.917
2) rpc=4.0.csz=1M wmm=64000 60.527 69.104 51.851 51.004
3) rpc=1.5.csz=1M wmm=64 69.804 10758.480 80.402 77.356
4) rpc=1.5.csz=1M wmm=64000 50.872 69.366 50.763 53.339
5) rpc=4.0.csz=1G wmm=64 84.117 7625.792 69.790 69.627
6) rpc=4.0.csz=1G wmm=64000 49.964 67.018 49.968 49.380
7) rpc=1.5.csz=1G wmm=64 68.567 3650.008 70.283 69.933
8) rpc=1.5.csz=1G wmm=64000 49.800 67.298 50.116 50.345
legend:
rpc := "random_page_cost"
csz := "effective_cache_size"
wmm := "work_mem"
如您所見, NOT IN()
變體對work_mem
不足非常敏感。 同意,設置64(KB)非常低,但這個“或多或少”對應於大數據集,它們也不適合哈希表。
EXTRA:在暖機階段, NOT EXISTS()
查詢遭受極端FK觸發器爭用。 這是與真空守護體發生沖突的結果,真空守護在表格設置后仍然有效:
PostgreSQL 9.1.2 on x86_64-unknown-linux-gnu, compiled by gcc (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1, 64-bit
NOT EXISTS()
QUERY PLAN
--------------------------------------------------------------------------------------------------------------------------------
Delete on one o (cost=6736.00..7623.94 rows=27962 width=12) (actual time=80.596..80.596 rows=0 loops=1)
-> Hash Anti Join (cost=6736.00..7623.94 rows=27962 width=12) (actual time=49.174..61.327 rows=27050 loops=1)
Hash Cond: (o.id = t.one_id)
-> Seq Scan on one o (cost=0.00..463.00 rows=30000 width=10) (actual time=0.003..5.156 rows=30000 loops=1)
-> Hash (cost=3736.00..3736.00 rows=240000 width=10) (actual time=49.121..49.121 rows=23600 loops=1)
Buckets: 32768 Batches: 1 Memory Usage: 1015kB
-> Seq Scan on two t (cost=0.00..3736.00 rows=240000 width=10) (actual time=0.006..33.790 rows=240000 loops=1)
Trigger for constraint two_one_id_fkey: time=467720.117 calls=27050
Total runtime: 467824.652 ms
(9 rows)
首先:你的文字說:
我想從
item_tbl
刪除那些孤兒記錄。
但是你的代碼說:
DELETE FROM link_tbl lnk ...
更新:在重新link_tbl
QI時,發現它更有可能要刪除link_tbl
孤立行。 行數指向該方向。 @Lucas )查詢在這種情況下是正確的。 但我擔心,在這種情況下, NOT EXISTS
實際上比NOT IN
慢 。
為了驗證我運行了一個測試用例,這就像你的設置一樣遠程。 無法使它大得多,或者SQLfiddle會遇到超時。
對於相反的情況, NOT EXISTS
會更快。 (我也測試了這一點。) EXISTS
更適合測試“很多”。 一般來說, EXISTS
比使用NOT EXISTS
獲得更多 - 這種形式無論如何都必須檢查整個表格。 這是更難證明的東西不存在 ,而不是證明的東西存在 。 這個普遍的事實也適用於數據庫。
此操作適合拆分。 特別是如果你有並發事務(但即使沒有),我會考慮將DELETE
分成幾個片段,這樣事務可以在相當長的時間后進行COMMIT
。
就像是:
DELETE FROM link_tbl l
WHERE l.item_id < 1000000
AND l.item_id NOT IN (SELECT i.id FROM item_tbl i)
然后l.item_id BETWEEN 100001 AND 200000
等
您無法使用函數自動執行此操作。 這會把所有東西都包裝成一個交易並且無視目的。 所以你必須從任何客戶端編寫腳本。
或者你可以使用..
這個附加模塊允許您在任何數據庫中運行單獨的事務,包括它運行的事務。這可以通過持久連接完成,這應該可以消除大部分連接開銷。 有關如何安裝它的說明:
如何在PostgreSQL中使用(安裝)dblink?
DO
會做這個工作(PostgreSQL 9.0或更高版本)。 一次為50000 item_id
運行100個DELETE
命令:
DO
$$
DECLARE
_sql text;
BEGIN
PERFORM dblink_connect('port=5432 dbname=mydb'); -- your connection parameters
FOR i IN 0 .. 100
LOOP
_sql := format('
DELETE FROM link_tbl l
WHERE l.item_id BETWEEN %s AND %s
AND l.item_id NOT IN (SELECT i.id FROM item_tbl i)'
, (50000 * i)::text
, (50000 * (i+1))::text);
PERFORM dblink_exec(_sql);
END LOOP;
PERFORM dblink_disconnect();
END
$$
如果腳本應該被中斷: dblink_connect
將DB執行的dblink_connect
寫入數據庫日志,這樣您就可以看到已完成的操作。
也許這個:
DELETE FROM link_tbl lnk
WHERE NOT EXISTS
( SELECT 1 FROM item_tbl item WHERE item.id = lnk.item_id );
處理大量記錄時,創建臨時表,執行INSERT INTO SELECT * FROM ...
然后刪除原始表,重命名臨時表,然后再添加索引可以更高效...
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.