在某些情况下,
在没有主键或其他唯一索引的表中,已经存在的记录的完整克隆会无意中掉落。

例如,将时间度量标准的值写入PostgreSQL COPY流,然后突然失败,并且部分完全相同的数据再次出现。
如何清除不必要的克隆数据库?
当PK不是助手时
最简单的方法是完全避免这种情况的发生。 例如,滚动相同的PRIMARY KEY。 但这并不总是可能的,而不增加存储的数据量。
例如,如果原始系统的精度高于数据库中字段的精度:
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)
然后我们的手颤抖了三下,Ctrl + V卡住了,现在...
首先,让我们了解一下我们的表可能非常大,因此,在找到所有克隆之后,建议我们从字面上“戳一下手指”删除
特定记录,而不必再次搜索它们 。
有一种方法-
通过ctid寻址 ,即特定记录的物理标识符。
也就是说,首先,我们需要在表行的全部内容的上下文中收集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;
是的,我们立即看到,如果阵列中有多个记录,那么就是所有克隆。 让我们只留下它们:
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[]);
检查一下自己:
[看explain.tensor.ru]是的,没错:我们选择了3条记录作为整个表的唯一Seq扫描,并且Delete节点使用
一次遍历使用Tid Scan搜索数据:
-> Tid Scan on tbl (actual time=0.050..0.051 rows=3 loops=1) TID Cond: (ctid = ANY ($0))
如果您清除了许多记录,
请不要忘记驱动VACUUM ANALYZE 。
让我们检查一张更大的桌子,花很多钱:
TRUNCATE TABLE tbl; INSERT INTO tbl SELECT chr(ascii('a'::text) + (random() * 26)::integer) k
[看explain.tensor.ru]因此,该方法可以成功工作,但是必须谨慎使用。 因为对于每条删除的记录,在“ Tid扫描”中有一个数据页读数,在“删除”中有一个数据页读数。