有一些情況,當 到沒有主鍵的表 或某些其他唯一索引,由於疏忽,已包含現有記錄的完整克隆。
例如,使用 COPY 流將時間順序度量的值寫入 PostgreSQL,然後突然發生故障,部分完全相同的資料再次到達。
當PK不是幫手時
最簡單的方法就是從一開始就防止這種情況發生。 例如,滾動主鍵。 但如果不增加儲存資料量,這並不總是可能的。
例如,如果來源系統的準確率高於資料庫中欄位的準確率:
metric | ts | data
--------------------------------------------------
cpu.busy | 2019-12-20 00:00:00 | {"value" : 12.34}
cpu.busy | 2019-12-20 00:00:01 | {"value" : 10}
cpu.busy | 2019-12-20 00:00:01 | {"value" : 11.2}
cpu.busy | 2019-12-20 00:00:03 | {"value" : 15.7}
你注意到了嗎? 倒數計時而不是 00:00:02 早一秒就用 ts 記錄在資料庫中,但從應用程式的角度來看仍然相當有效(畢竟資料值不同!)。
你當然可以做到 PK(公制,ts) - 但隨後我們會遇到有效資料的插入衝突。
可以做 PK(公制、ts、數據) - 但這會大大增加它的體積,我們不會使用它。
因此,最正確的選擇是製作常規非唯一索引 (公制,ts) 並在出現問題後進行處理。
“複製戰爭已經開始”
發生了某種意外,現在我們必須從表中銷毀克隆記錄。
讓我們對原始資料進行建模:
CREATE TABLE tbl(k text, v integer);
INSERT INTO tbl
VALUES
('a', 1)
, ('a', 3)
, ('b', 2)
, ('b', 2) -- oops!
, ('c', 3)
, ('c', 3) -- oops!!
, ('c', 3) -- oops!!
, ('d', 4)
, ('e', 5)
;
這裡我們的手顫抖了三下,Ctrl+V被卡住了,現在...
首先,我們要明白,我們的表可能非常大,所以在找到所有克隆後,建議我們直接「戳一下手指」來刪除 具體記錄無需研究.
還有這樣一個方法──這個
也就是說,首先我們需要收集表格行完整內容上下文中所記錄的ctid。 最簡單的選擇是將整行轉換為文字:
SELECT
T::text
, array_agg(ctid) ctids
FROM
tbl T
GROUP BY
1;
t | ctids
---------------------------------
(e,5) | {"(0,9)"}
(d,4) | {"(0,8)"}
(c,3) | {"(0,5)","(0,6)","(0,7)"}
(b,2) | {"(0,3)","(0,4)"}
(a,3) | {"(0,2)"}
(a,1) | {"(0,1)"}
可以不投嗎?原則上,大多數情況下是可以的。 直到您開始使用此表中的字段 不帶相等運算子的類型:
CREATE TABLE tbl(k text, v integer, x point);
SELECT
array_agg(ctid) ctids
FROM
tbl T
GROUP BY
T;
-- ERROR: could not identify an equality operator for type tbl
是的,我們立即看到,如果數組中有多個條目,那麼這些都是克隆。 讓我們離開他們:
SELECT
unnest(ctids[2:])
FROM
(
SELECT
array_agg(ctid) ctids
FROM
tbl T
GROUP BY
T::text
) T;
unnest
------
(0,6)
(0,7)
(0,4)
對於那些喜歡寫短的人你也可以這樣寫:
SELECT
unnest((array_agg(ctid))[2:])
FROM
tbl T
GROUP BY
T::text;
由於我們對序列化字串本身的值不感興趣,因此我們只是將其從子查詢的傳回列中丟棄。
還剩下一點點要做 - 讓 DELETE 使用我們收到的集合:
DELETE FROM
tbl
WHERE
ctid = ANY(ARRAY(
SELECT
unnest(ctids[2:])
FROM
(
SELECT
array_agg(ctid) ctids
FROM
tbl T
GROUP BY
T::text
) T
)::tid[]);
我們自己檢查一下:
是的,一切都正確:我們選擇了3筆記錄進行全表唯一的Seq Scan,並使用Delete節點來搜尋數據 使用 Tid Scan 進行單次掃描:
-> Tid Scan on tbl (actual time=0.050..0.051 rows=3 loops=1)
TID Cond: (ctid = ANY ($0))
如果你清除了很多記錄,
讓我們檢查一下是否有更大的表和更多的重複項:
TRUNCATE TABLE tbl;
INSERT INTO tbl
SELECT
chr(ascii('a'::text) + (random() * 26)::integer) k -- a..z
, (random() * 100)::integer v -- 0..99
FROM
generate_series(1, 10000) i;
因此,該方法很有效,但必須謹慎使用。 因為每刪除一筆記錄,Tid Scan 都會讀取一個資料頁,Delete 也會讀取一個資料頁。
來源: www.habr.com