Mysql 在具有10亿行的表中查找重复项

Mysql 在具有10亿行的表中查找重复项,mysql,sql,Mysql,Sql,在一个包含10亿行的表中,我需要在5列中的2列中找到重复的条目 详细内容: 两列上的重复条目意味着:a列可以有重复条目,b列可以有重复条目,但同时考虑的两列不能有重复条目 原因是: 我需要找出在数据回填期间错误插入的重复记录,因为回填是在表没有主键的情况下完成的 该表包含以下列: id, warehouse, quantity, date, updated by 需要在id+仓库上找到重复条目 我试着用 select id, warehouse from my_table group by i

在一个包含10亿行的表中,我需要在5列中的2列中找到重复的条目

详细内容:

两列上的重复条目意味着:a列可以有重复条目,b列可以有重复条目,但同时考虑的两列不能有重复条目

原因是:

我需要找出在数据回填期间错误插入的重复记录,因为回填是在表没有主键的情况下完成的

该表包含以下列:

id, warehouse, quantity, date, updated by
需要在id+仓库上找到重复条目

我试着用

select id, warehouse
from my_table
group by id, warehouse
having count(*) > 1
这并没有给我id和仓库组合的副本

我害怕在桌子上做自我连接,因为在这么大的桌子上操作会花费太长时间

请帮我找出复制品的最快方法

另外,作为一个额外的挑战,我需要删除重复条目(在表中只保留重复条目的一条记录)。在这张巨大的桌子上有没有快速的方法

当我试图设置主键时,查询在“复制到tmp表”步骤上停留了大约48小时,表上的元数据锁阻止了任何插入

有关数据库的详细信息:

引擎InnoDB

服务器mysql


RAM 7.5GB将为您完成以下工作:

INSERT INTO newtable(id,warehouse)
SELECT DISTINCT id,warehouse
FROM my_table
确认newtable是正确的

DROP TABLE my_table;
RENAME TABLE newtable TO my_table

这并不漂亮,但它可能比大多数其他解决方案都要快,因为据我所知,您需要找到重复的条目,然后对它们进行处理。正如评论员已经指出的那样,如果您可以添加索引并在生产时间之外运行self-join,那么我肯定会继续这样做

如果另一方面,你不能有效地,那么你就面临着一个扣合问题。没有索引的自联接的问题是(至少在SQL Server中)可能会使用嵌套循环联接来生成结果集。我在这里假设mysql将使用相同的逻辑。嵌套循环有一个很大的O/O(n^2),对于您这样大小的数据集,这将非常昂贵

如果我们假设仓库列的基数相对较小,那么最好的方法是按仓库将数据拆分为子集,并对每个子集执行检查

在最坏的情况下,select语句将是O(n),即全表扫描。如果仓库列上有索引,那么选择值应该接近O(log(n))

然后有两种方法来解决您的问题。。 1:为每个仓库创建一个新表,并将主表中的所有数据插入其中。对于select,此操作的时间应为O(log(n))*num,对于新表的每个插入,此操作的时间应为O(1)。创建新表后,请在每个表上创建索引。这将是我的首选方法

2:如果出于任何原因这是不可能的,您可以使用基于代码的方法。这里的关键是使用具有O(1)操作时间的哈希集。我在下面用C#提供了一些示例代码来说明如何实现这一点

    using System.Collections.Generic;
    using System.Linq;

    namespace ConsoleApplication9
    {
        class Program
        {
            /// <summary>
            /// represents the totality of all records in the database
            /// </summary>
            static List<WareHouse> wareHouseItemsList = new List<WareHouse>(); 

            static void Main(string[] args)
            {
                AddWareHouseValues(); // simulate the full table by populating some values

                for (var i = 0; i < 2; i++)
                {
                    // simulate retriveing the data from db - this is O(log(n)) assuming an index on warehouse id
                    var individualWarehouseItems = from item in wareHouseItemsList
                        where item.WarehouseID == 1
                        select item.ItemID;

                    var integerSet = new HashSet<int>();
                    var list = integerSet.AddRange(individualWarehouseItems);  // Hashset operations are O(1)
                    // do something with list .... 
                }

            }


            static void AddWareHouseValues()
            {            
                wareHouseItemsList.Add(new WareHouse {WarehouseID = 1, ItemID = 1}); // create a duplicate for WH 1
                wareHouseItemsList.Add(new WareHouse { WarehouseID = 2, ItemID = 11 }); // create a duplicate for WH 2

                for (var j = 1; j < 3; j++)
                {
                    for (var i = 1; i < 20; i++)
                    {
                        wareHouseItemsList.Add(new WareHouse {WarehouseID = j, ItemID = i});
                    }
                }
            }
        }



        public class WareHouse
        {
            public int WarehouseID { get; set; }
            public int ItemID { get; set; }
        }

        public static class Extensions
        {
            /// <summary>
            /// Tries to add a range of intergers to a hashset and returns any that failed
            /// </summary>
            /// <param name="this">hashset</param>
            /// <param name="items">collection of integers</param>
            /// <returns></returns>
            public static IEnumerable<int> AddRange(this HashSet<int> @this, IEnumerable<int> items)
            {
                foreach (var item in items)  // This is O(n) however n here is much smaller than full dataset
                {
                    var allAdded = true;
                    if (!(allAdded &= @this.Add(item)))  // This is O(1)
                    {
                        yield return item;
                    }
                }

            }
        }
    }
使用System.Collections.Generic;
使用System.Linq;
命名空间控制台应用程序9
{
班级计划
{
/// 
///表示数据库中所有记录的总和
/// 
静态列表wareHouseItemsList=新列表();
静态void Main(字符串[]参数)
{
AddWareHouseValues();//通过填充一些值来模拟整个表
对于(变量i=0;i<2;i++)
{
//模拟从数据库检索数据-假设仓库id上有索引,这是O(log(n))
var individualWarehouseItems=来自WarehouseItems列表中的项目
其中item.WarehouseID==1
选择item.ItemID;
var integerSet=new HashSet();
var list=integerSet.AddRange(individualWarehouseItems);//哈希集操作为O(1)
//用列表做点什么。。。。
}
}
静态void AddWareHouseValues()
{            
Add(新仓库{WarehouseID=1,ItemID=1});//为WH 1创建一个副本
Add(新仓库{WarehouseID=2,ItemID=11});//为WH 2创建一个副本
对于(var j=1;j<3;j++)
{
对于(变量i=1;i<20;i++)
{
Add(新仓库{WarehouseID=j,ItemID=i});
}
}
}
}
公共类仓库
{
public int WarehouseID{get;set;}
公共int ItemID{get;set;}
}
公共静态类扩展
{
/// 
///尝试向哈希集添加一系列整数,并返回所有失败的整数
/// 
///哈希集
///整数集合
/// 
公共静态IEnumerable AddRange(this HashSet@this,IEnumerable items)
{
foreach(items中的var item)//这是O(n),但是这里的n比完整的数据集小得多
{
var allAdded=真;
if(!(allAdded&=@this.Add(item))//这是O(1)
{
收益回报项目;
}
}
}
}
}

我已经在MSSQL中创建了一个解决方案,但它在MySQL中会运行得更好,因为MySQL中的偏移量限制不需要设置Order by

解决方案是最慢的,但您可以在夜间运行它,在早上停止它,然后在重新启动时从剩下的地方继续。而不是我
CREATE TABLE findduplicates(id NVARCHAR(50), warehouse NVARCHAR(50), occurence INT)
CREATE UNIQUE INDEX uniqueIndex ON findduplicates(id,warehouse); 

CREATE TABLE integerValue(id int)
INSERT INTO integerValue VALUES(0)

DECLARE @i INT
SELECT @i = id from integerValue

DECLARE @id NVARCHAR(50)
DECLARE @warehouse NVARCHAR(50)


SELECT
    @id = id,
    @warehouse = warehouse
FROM 
    duplicatest
ORDER BY
    1
OFFSET @i ROWS 
FETCH NEXT 1 ROWS ONLY;

WHILE(@id IS NOT NULL)
BEGIN
IF EXISTS(SELECT TOP 1 1 FROM findduplicates WHERE id = @id and warehouse = @warehouse)
BEGIN
    UPDATE findduplicates SET occurence = occurence + 1 WHERE id = @id and warehouse = @warehouse
END
ELSE
BEGIN
    INSERT INTO findduplicates VALUES(@id,@warehouse,1)
END

SET @id = NULL
SET @i = @i + 1
UPDATE integerValue SET id = @i

SELECT
    @id = id,
    @warehouse = warehouse
FROM 
    duplicatest
ORDER BY
    1
OFFSET @i ROWS 
FETCH NEXT 1 ROWS ONLY;
END