从数百万条记录中删除重复行的有效方法

Effective way to delete duplicate rows from millions of records

我正在寻找一种有效的方法来从我的数据库中删除重复的记录。首先,我使用了一个使用连接等的存储过程,这导致查询执行得非常慢。现在,我正在尝试一种不同的方法。请考虑以下查询:

/* QUERY A */

SELECT *
FROM my_table
WHERE col1 = value
  AND col2 = value
  AND col3 = value

这个查询只用了 12 秒,结果是 182.400 条记录。 table 中的行数当前为 420.930.407,col1 和 col3 已编入索引。

下一个查询:

/* QUERY B */

WITH ALL_RECORDS AS
  (SELECT id
   FROM my_table
   WHERE col1 = value
     AND col2 = value
     AND col3 = value)
SELECT *
FROM ALL_RECORDS

这个查询用了不到 2 秒,并为我提供了 table 中 182.400 条记录的所有 ID(根据 where 子句)。

然后,我的最后一个查询是 select 是在我要分组以检查重复项的列上分组的所有记录的最低(第一个)id 的查询:

/* QUERY C */

SELECT MIN(id)
FROM my_table
WHERE col1 = value
  AND col2 = value
  AND col3 = value
GROUP BY col1,
         col2,
         col3,
         col4,
         col5,
         col6

同样,此查询的执行时间不到 2 秒。结果是30.400,也就是说182.400条唯一记录中有30.400条唯一记录。

现在,我想删除(或者,首先 select 以确保我的查询正确)所有不唯一的记录。所以,我想从 my_table.

中删除 182.400 - 30.400 = 152.000 条记录

我想我会合并最后两个查询:根据 col1、col2 和 col3 的 where 子句获取属于我的数据集的所有 id(查询 B),然后 delete/select 来自该数据集的 ID 不在唯一记录 ID 的 ID 列表中(查询 C)。

然而,当我 select all from query B where query B.id NOT IN query C 时,查询不需要 2、4 或 12(14 或 16)秒,但似乎永远(1 分钟后显示 20.000 条记录,2 分钟后显示大约 40.000 条记录,所以我取消了查询,因为它会找到 152.000 条记录,这样需要 8 分钟)。

WITH ALL_RECORDS AS
  (SELECT id
   FROM my_table
   WHERE col1 = value
     AND col2 = value
     AND col3 = value)
SELECT id
FROM ALL_RECORDS
WHERE id NOT IN
    (SELECT MIN(id)
     FROM my_table
     WHERE col1 = value
       AND col2 = value
       AND col3 = value
     GROUP BY col1,
              col2,
              col3,
              col4,
              col5,
              col6)

我知道 NOT IN 很慢,但我不明白它是怎么这么慢的(因为没有 not 部分的两个查询每个执行不到 2 秒)。

有没有人对我如何解决这个难题有一些好的建议?

----------------附加信息----------------

以前的解决方案是以下存储过程。出于某种原因,它在我的验收环境中完美执行,但在我的生产环境中却没有。目前,我们有超过 4 亿条生产记录和略多于 200 万条验收记录,所以这可能是一个原因。

DELETE my_table
FROM my_table
LEFT OUTER JOIN
  (SELECT MIN(id) AS RowId,
          col1,
          col2,
          col3,
          col4,
          col5,
          col6
   FROM my_table
   WHERE col1 = value
     AND col2 = value
     AND col3 = value
   GROUP BY col1,
            col2,
            col3,
            col4,
            col5,
            col6) AS KeepRows ON my_table.id = KeepRows.RowId
WHERE KeepRows.RowId IS NULL
  AND my_table.col1 = value
  AND my_table.col2 = value
  AND my_table.col3 = value

我已将此解决方案基于 Whosebug 上的另一个答案(目前找不到),但我觉得我应该能够创建一个基于查询 B 和 C 的查询,该查询可在几秒钟内执行。 ..

将两个 2 秒查询组合在一起通常不会产生单个 4 秒查询,因为查询与其基础 tables 不同,很少被索引。

此类任务的通常方法是缓存 id 的你想保留在临时 table 中,相应地索引它,然后在 left join 中使用它(或 not in - 我敢打赌最终的执行计划几乎是一样的)。

如果您在主 table 上使用索引,您可能会获得更多性能。例如,我认为 (col1, col2, col3) 应该给你的代码一些提升(不一定要按此顺序提及列,通常取决于它们的基数)。

with dupl as (
select row_number() over(partition by col1,col2,col3,col4,col5,col6 order by id) rn,
id,col1,col2,col3,col4,col5,col6
from myTable
)
delete dupl where rn>1