比较内存中的 2 个无序记录集

Posted

技术标签:

【中文标题】比较内存中的 2 个无序记录集【英文标题】:Compare 2 unordered recordset in memory 【发布时间】:2018-07-12 23:38:53 【问题描述】:

下面是我的应用程序数据库表,其中包含存储在表中的 SQL 查询:QueryStorage

Id           Query           ConnectionString       Rdbms
1            select...        Data Source           Sql Server
2            select...        Data Source           Oracle

上表中的 SQL 查询是通过 Web 服务更新的,我们不允许在查询之上进行更新,尽管我们可以在查询之上添加一些内容,如下所示:

查询存储在表中: select id as LinkedColumn, Amount as CompareColumn from Source

从我的 c# 应用调整查询:select Q.LinkedColumn, Q.CompareColumn from (stored sql query) as Q

我正在尝试比较如下 2 个无序列表:

QueryStorage 表 记录中为Id = 1(Sql server) 执行的查询如下:

select Id as LinkedColumn,CompareColumn from Source

清单 1:

LinkedColumn     CompareColumn
1                100
2                200
3                300
4                400
5                500
6                600
7                700
8                800
9                900
10               1000

QueryStorage 表记录中为 Id = 2(Oracle) 执行的查询如下:

select Id as LinkedColumn,CompareColumn from Target

清单 2:

LinkedColumn       CompareColumn
10                 10
9                  20
8                  30
7                  40
6                  50
5                  60
4                  70
3                  80
2                  90
1                  5

我想加入LinkedColumn from source to target,然后在CompareColumn 上进行比较,这应该会给我以下输出:

SrcLinkedColumn      SrcCompareColumn     TgtLinkedColumn    TgtCompareColumn
    1                       100           1                  5
    2                       200           2                  90               

逻辑:

var data = (from s in List1.AsEnumerable()
             join t in List2.AsEnumerable() on s.Field<string>("LinkedColumn") equals t.Field<string>("LinkedColumn")
             where s.Field<decimal>("CompareColumn") != t.Field<decimal>("CompareColumn")
                        select new
                        
                            srcLinkedcol = s.Field<string>("LinkedColumn"),
                            srcCompareCol = s.Field<decimal>("CompareColumn"),
                            tgtLinkedCol = t.Field<string>("LinkedColumn"),
                            tgtCompareCol = t.Field<decimal>("CompareColumn")
                        ).ToList();

从源到目标将有数百万条记录,我想与out of memory exception 比较大的问题,我们现在通过将所有数据加载到内存中然后与上面的 linq 查询进行比较。

p>

我想到了以下两种解决方案:

1) 打开 2 个有序数据阅读器。

优点:

    - No memory exception
    - Fast as there will be 1 to 1 comparision of LinkedColumn for List1 and 
      List2 records.

缺点:

    - Order by is require on LinkedColumns and as i have no control over 
      query as because it is dumped by webservice in QueryStorage table so 
      user is explicitly require to submit query with order by on 
      LinkedColumn.
    - Wont work if order by on Linkedcolumn is not present.
    - Order by query have performance overhead so because of this user may not include order by on LinkedColumn in query.                               

2) 通过调整查询并添加OffSet and FetchRowNext 逐块比较记录。这就是我对算法的看法:

但我仍然觉得使用第二种方法我会遇到内存异常问题,因为在某些步骤中,源和目标的数据不匹配,我会将它们存储在缓冲区(数据表或列表等)中以进行下一个块比较。

谁能指导我解决这个问题的好算法或更好的方法?

注意:我不想使用 LinkedServerSSIS

【问题讨论】:

【参考方案1】:

您的第二个解决方案看起来像是重新发明External merge sort 的尝试。如果您的数据不适合内存,这是一种有效的方法,但当您的数据集适合内存时,它就有点过分了。

2400 万行,每行有两个数字(8 个字节),只有约 200MB 的内存。 两个数据集是 400MB。这在现代桌面硬件上算不了什么。

将两个数据集加载到内存中。在简单的数组中。对内存中的数组进行排序。 通过扫描两个排序数组一次来找出差异。 你不需要花哨的 LINQ。

这是伪代码。我们有Array1Array2。维护两个包含每个数组当前索引的变量:Idx1Idx2。沿着数组移动索引以查找 LinkedColumn 匹配的位置。

Idx1 = 0; Idx2 = 0;
while (true)

    // scan arrays until both indexes point to the same LinkedColumn
    // here we assume that both arrays are sorted by LinkedColumn
    // and that values in LinkedColumn are unique

    while (Idx1 < Array1.Length && Idx2 < Array2.Length &&
        Array1[Idx1].LinkedColumn < Array2[Idx2].LinkedColumn)
    
        // move along Array1
        Idx1++;
    

    while (Idx1 < Array1.Length && Idx2 < Array2.Length &&
        Array1[Idx1].LinkedColumn > Array2[Idx2].LinkedColumn)
    
        // move along Array2
        Idx2++;
    

    // at this point both indexes point to the same LinkedColumn
    // or one or both of the arrays are over

    if (Idx1 >= Array1.Length || Idx2 >= Array2.Length)
    
        break;
    

    // compare the values
    if (Array1[Idx1].CompareColumn != Array2[Idx2].CompareColumn)
    
        // TODO: output/save/print the difference
    

    Idx1++; Idx2++;

您可以在两个表T1T2 中将两个数据集转储到您选择的数据库中,在两个表中的LinkedColumn 上创建唯一索引并运行此查询:

SELECT
     T1.LinkedColumn  AS SrcLinkedColumn
    ,T1.CompareColumn AS SrcCompareColumn
    ,T2.LinkedColumn  AS DstLinkedColumn
    ,T2.CompareColumn AS DstCompareColumn
FROM
    T1 INNER JOIN T2 ON T1.LinkedColumn = T2.LinkedColumn
WHERE
    T1.CompareColumn <> T2.CompareColumn
ORDER BY
    T1.LinkedColumn
;

上面的伪代码执行的合并连接与 DBMS 服务器将为此查询执行的合并连接相同。

【讨论】:

当我尝试将这 1200 万条记录加载到数组和列表中时,出现内存不足异常。 @User,12 * 8 = 9600 万字节。很难相信您的计算机没有 100MB 的可用内存。我建议你问另一个关于在内存中加载这些数据的问题。在那里显示您的代码,有人将能够弄清楚发生了什么。 @User,我在这里说的是每条记录 8 个字节,因为您在问题中的示例数据是简单的整数。如果您的实际数据不同,请更新问题并告诉我们它是什么。 @User VladimirBaranov 是正确的,您应该能够毫无问题地加载该数据。除非你有硬件瓶颈。确保您至少有 2GB 的可用内存。从您的 VS 转到项目属性并在 Build 上确保您的平台目标设置为 x64 @User 我仍然更喜欢使用数据库引擎而不是 C# 来维护查询。所以,Vladimir Baranov 的第二个建议更可取(在我看来)。【参考方案2】:

如果我理解正确,您可以通过DataReader 之类的方式在C# 中获取表格数据。如果您存储从 Web 服务获取的记录,然后像@VladimirBaranov 提到的那样执行一些查询,则存储时间太长而且不合理。

我认为在这种情况下比较的更好解决方案是Binary Search。它的时间顺序是O(log n),它的内存顺序是O(1)。所以它不需要额外的内存。

你可以像下面这样使用二分搜索:

1- 对较小的表进行排序。

时间顺序:如果这个表有n个元素,平均来说,执行时间是O(nlogn)(more information)。

内存顺序:O(1),因为 C# 中的 sort 方法是 In-place

2- 通过二分查找将另一个表记录与排序表进行比较。

时间顺序:如果另一个表有m条记录,则执行时间为O(m)*O(logn) = O(mlogn)。 (more information)

内存顺序:不需要额外的内存。

总之,我感谢您的问题可能是最好的解决方案,但我认为这个解决方案是一个很好的解决方案,它具有O(mlogn) 执行时间,O(1) 内存顺序,它仅在 C# 中实现,无需将记录保存在数据库。

【讨论】:

那么,如果我谈论您的第二种方法,那么我是否需要将 2 个结果集放入内存然后进行比较? 为了比较,你需要把它们都放在内存中,但根据我的解决方案,你需要的额外内存是O(1)。 @VladimirBaranov 等其他解决方案需要 O(n*m) 额外内存。 但是,如果我将有多个并行运行的作业,比如说 8-10 个作业,每个作业将处理 2400 万个数据,因此您可以想象内存中的数据量有时会有 8-10 个工作或更多工作,那么内存将如何处理这些数据? 很清楚。您的数据接近Big data。在这种情况下,所有算法都面临许多挑战。你需要更强大的服务器来做到这一点。像我的算法这样的所有算法都可以尽可能优化内存或执行时间,它们不能处理所有情况。【参考方案3】:

您可能由于内存碎片而内存不足。

确保您使用有效的方式来读取数据,例如 DataReader。 为避免碎片化,请先获取记录的计数并使用此计数初始化 List 1 数组,然后再开始插入数组。如果您无法获得计数,您可以增加列表的增长因子或构建一个包含原始查询作为子查询的查询,如下所示: 从([您的原始查询])中选择 COUNT(*) 构建包含您的字段的结构数组或二维数组,这不需要每行的引用。 此外,您可以将原始(列表 1)数据存储为目标格式。这样一来,您的内存中就只有一个数组。

这是伪代码:

获取 List1 的行数: 创建一个二维数组来保存list1的数据 从 List1 中获取数据并将这些记录添加到您的数组中 为您提供第一个数据读取器 使用就地排序对 list1 数据进行排序(这样您就可以使用二进制搜索来查找 list1 行)。 从List2中获取数据 对于 List2 的每一行,使用二进制搜索在 list1 中查找相应记录:将 List2 的比较值添加到匹配行(记住您的数组已经包含您的 3 列)。您只需要设置list2)的比较值。 通过删除不匹配的行来清理您的结果。我建议您使用相同的数组进行清理。使用两个索引,您可以扫描数组并在数组中向上移动记录。

【讨论】:

【参考方案4】:

对不起,我只能给你一个想法:

约束:

目标拥有数百万条记录 来源有数百万条记录 内存有限

想法:

    为此使用至少 2 台机器 一个作为[Source],另一个作为[Target],有匹配服务 通过调用服务从[Source]发送每n(块)记录到[Target]机器,让它匹配记录,然后将结果发送回[Source]

奖金:

    使用超过 1 台 [Target] 机器,并让 [Source] 机器在异步和负载均衡的场景中使用它们

您可能会发现一些类似(或高级)的解决方案,例如:

[Target] 和 [Source] 可以使用共享内存(例如缓存服务器)来存储结果 在它们之间使用真正的负载平衡器层来处理对多台 [Target] 机器的多个请求 使用队列/消息层 即使将多个 [Source] 与多个 [Target] 机器一起使用

很抱歉,但(至少对我来说)这个案子太大了,只能用一台机器处理

我希望这至少能给你一个想法

【讨论】:

【参考方案5】:

删除匹配的元素怎么样?比如:

List1.RemoveAll(x => List2.Any(y => y.LinkedColumn == x.LinkedColumn && y.CompareColumn == x.CompareColumn))

【讨论】:

我的源和目标中有 2400 万条记录。我已经测试过,这将引发内存异常 如果您从源和目标加载相同范围的 LinkedColumn 怎么办...选择 Id 作为 LinkedColumn,CompareColumn from Source where LinkedColumn >=1 和 LinkedColumn 【参考方案6】:

似乎LinkedColumn 包含每个源上的唯一值,我会将这两个数据加载到本地数据库的两个表中,在LinkedColumn 上创建索引并进行连接。 p>

或者您可以将这两个数据加载到一个简单的文件中,在每一行包括一个源标识符,如下所示(不包括标题):

LinkedColumn 标识符 CompareColumn 1 秒 100 2 S 200 3 秒 300 ... 4 O 70 3 O 80 2 O 90

S 代表 SQL Server,O 代表 Oracle。 对文件进行排序(也许您可以运行 OS 排序或其他外部排序)。逐行读取已排序的文件。必须存储第一行,以便您将其LinkedColumn 与第二行进行比较以进行匹配,从而收集它们或存储第二行并删除第一行以在第三行中查找匹配,依此类推。

希望对你有帮助。

【讨论】:

【参考方案7】:

把这项工作留给 SQL Server。

让它处理 WHERE CLAUSE

T1.CompareColumn &lt;&gt; T2.CompareColumn

【讨论】:

最佳实践!这是最快的方式。 如果源数据来自 Sql server 而目标数据来自 Oracle 怎么办,然后告诉我这将如何工作? 我已经在我的问题中提到我的源数据来自 sql server 而目标来自 oracle

以上是关于比较内存中的 2 个无序记录集的主要内容,如果未能解决你的问题,请参考以下文章

Ms Access 比较两个记录集

WHERE IN 在 Access VBA 中使用两个记录集进行查询

游标是不是将 SELECT 结果记录集存储在内存中?

比较两个记录集变量给出类型不匹配

从 C# 中的存储过程返回多个记录集

在内存中,独立,断开连接的 ADO 记录集