在内存中加载大量结果集的最佳方法是什么?

时间:2018-02-14 11:04:41

标签: c# ado.net bigdata datareader

我正在尝试加载来自不同RDBMS的2个巨大的结果集(源和目标),但我正在努力解决的问题是在内存中获得这两个巨大的结果集。

下面考虑从源和目标中提取数据的查询:

  Sql Server -  select Id as LinkedColumn,CompareColumn from Source order by LinkedColumn

  Oracle -select Id as LinkedColumn,CompareColumn from Target order by LinkedColumn

来源记录: 12377200

目标中的记录: 12266800

以下是我尝试过的一些统计方法:

1) 2开放式数据读取器方法,用于读取源数据和目标数据

Total jobs running in parallel = 3

Time taken by Job1 = 01:47:25

Time taken by Job1 = 01:47:25

Time taken by Job1 = 01:48:32

There is no index on Id Column.

Major time is spend here :


var dr = command.ExecuteReader();

Problems : 
There are timeout issues also for which i have to kept `commandtimeout` to 

0(infinity)这很糟糕。

2)通过块读取方法读取源和目标数据:

   Total jobs = 1
   Chunk size : 100000
   Time Taken : 02:02:48
   There is no index on Id Column.

3)通过块读取方法读取源和目标数据:

   Total jobs = 1
   Chunk size : 100000
   Time Taken : 00:39:40
   Index is present on Id column.

4) 2开放式数据读取器方法,用于读取源数据和目标数据:

   Total jobs = 1
   Index : Yes
   Time: 00:01:43

5) 2开放式数据读取器方法,用于读取源数据和目标数据:

   Total jobs running in parallel = 3
   Index : Yes
   Time: 00:25:12

我确实观察到虽然在LinkedColumn上有索引确实提高了性能,但问题是我们正在处理可能具有索引或可能没有索引的第三方RDBMS表。

我们希望数据库服务器尽可能免费,因此数据阅读器方法似乎并不是一个好主意,因为会有大量并行运行的作业会给我们不想要的数据库服务器带来太大的压力。

因此,我们希望从源到目标获取资源内存中的记录,并进行1-1比较记录比较,保持数据库服务器免费。

注意:我想在我的c#应用中执行此操作,并且不想使用SSISLinked Server

更新:

源sql查询sql server management studio中的执行时间:00:01:41

目标Sql查询sql server management studio中的执行时间:00:01:40

在内存中读取大量结果集的最佳方法是什么?

代码:

static void Main(string[] args)
        {   
            // Running 3 jobs in parallel
             //Task<string>[] taskArray = { Task<string>.Factory.StartNew(() => Compare()),
        //Task<string>.Factory.StartNew(() => Compare()),
        //Task<string>.Factory.StartNew(() => Compare())
        //};
            Compare();//Run single job
            Console.ReadKey();
        }
public static string Compare()
        {
            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            var srcConnection = new SqlConnection("Source Connection String");
            srcConnection.Open();
            var command1 = new SqlCommand("select Id as LinkedColumn,CompareColumn from Source order by LinkedColumn", srcConnection);
            var tgtConnection = new SqlConnection("Target Connection String");
            tgtConnection.Open();
            var command2 = new SqlCommand("select Id as LinkedColumn,CompareColumn from Target order by LinkedColumn", tgtConnection);
            var drA = GetReader(command1);
            var drB = GetReader(command2);
            stopwatch.Stop();
            string a = stopwatch.Elapsed.ToString(@"d\.hh\:mm\:ss");
            Console.WriteLine(a);
            return a;
        }
      private static IDataReader GetReader(SqlCommand command)
        {
            command.CommandTimeout = 0;
            return command.ExecuteReader();//Culprit
        }

7 个答案:

答案 0 :(得分:3)

(我知道)比DataReader更快地获取数据库记录。

使用大型数据库带来了挑战,在2秒内读取1000万条记录非常好。

如果你想要更快,你可以:

  1. jdwend的建议:
  2.   

    使用sqlcmd.exe和Process类运行查询并将结果放入csv文件,然后将csv读入c#。 sqlcmd.exe旨在存档大型数据库,运行速度比c#接口快100倍。使用linq方法也比SQL Client类

    更快
    1. 平行查询并同时获取合并结果:https://shahanayyub.wordpress.com/2014/03/30/how-to-load-large-dataset-in-datagridview/

    2. 最简单的(对于SELECT * all而言最好的IMO)就是抛出硬件: https://blog.codinghorror.com/hardware-is-cheap-programmers-are-expensive/

    3. 另外,请确保您在发布模式下测试PROD硬件,因为这可能会影响基准测试。

答案 1 :(得分:0)

这是我使用的模式。它将特定记录集的数据转换为System.Data.DataTable实例,然后关闭并尽快处理所有未管理的资源。模式也适用于System.Data包括System.Data.OleDbSystem.Data.SqlClient等其他提供商。我相信Oracle Client SDK实现了相同的模式。

// don't forget this using statements
using System.Data;
using System.Data.SqlClient;

// here's the code.
var connectionstring = "YOUR_CONN_STRING";
var table = new DataTable("MyData");
using (var cn = new SqlConnection(connectionstring))
{
    cn.Open();
    using (var cmd = cn.CreateCommand())
    {
        cmd.CommandText = "Select [Fields] From [Table] etc etc";
                          // your SQL statement here.
        using (var adapter = new SqlDataAdapter(cmd))
        {
            adapter.Fill(table);
        } // dispose adapter
    } // dispose cmd
    cn.Close();
} // dispose cn

foreach(DataRow row in table.Rows) 
{
    // do something with the data set.
}

答案 2 :(得分:0)

我想我会以不同的方式处理这个问题。

但在我们做出一些假设之前:

  • 根据您的问题描述,您将从SQL Server和Oracle
  • 获取数据
  • 每个查询都会返回一堆数据
  • 您没有指定在内存中获取所有数据的重点是什么,也没有使用它。
  • 我认为您将处理的数据将被多次使用,您不会多次重复这两个查询。
  • 无论您对数据做什么,都可能不会同时向用户显示。

有了这些基础,我会处理以下内容:

  • 将此问题视为数据处理
  • 拥有第三个数据库或其他具有辅助数据库表的地方,您可以在其中存储2个查询的所有结果。
  • 为了避免超时,请尝试使用pagging获取数据(一次获取数千个)并保存在这些辅助DB表中,而不是“RAM”内存中。
  • 一旦您的逻辑完成所有数据加载(导入迁移),您就可以开始处理它。
  • 数据处理是数据库引擎的关键点,它们是高效的,并且在很多年内有很多演变,不用花时间重新发明轮子。使用一些存储过程将2个辅助表“压缩/处理/合并”为仅1。
  • 现在您已将所有“合并”数据放在第3个辅助表中,现在您可以使用它来显示或需要使用它的其他内容。

答案 3 :(得分:0)

如果您想更快地阅读它,您必须使用原始API来更快地获取数据。避免像linq这样的框架并依赖DataReader那个。尝试检查你需要的东西,比如脏读(在sql server中使用(nolock))。

如果您的数据非常庞大,请尝试实现部分读取。像为数据制作索引之类的东西。也许你可以把条件放在从 - 到所有选定的日期之前。

之后,您必须考虑在系统中使用线程来并行化流程。实际上1个线程从作业1中获取,另一个线程从作业2中获取。这个将耗费大量时间。

答案 4 :(得分:0)

除了技术方面,我认为这里存在一个更基本的问题。

  

select [...] order by LinkedColumn

     

我确实观察到虽然在LinkedColumn上有索引确实提高了性能,但问题是我们正在处理可能具有索引或可能没有索引的第三方RDBMS表。

     

我们希望尽可能免费保留数据库服务器

如果您无法确保数据库在该列上具有基于树的索引,则意味着数据库将非常忙于对数百万个元素进行排序。它很慢而且资源很耗力。摆脱SQL语句中的order by并在应用程序端执行它以更快地获得结果并减少DB上的负载...或确保DB具有这样的索引!!!

...取决于此提取是常见操作还是罕见操作,您需要在数据库中强制执行适当的索引,或者只是获取所有索引并对其进行客户端排序。

答案 5 :(得分:0)

多年前我也遇到过类似的情况。在我查看问题之前,连续运行了5天,使用SQL在两个系统之间移动数据。

我采取了不同的方法。

我们将来自源系统的数据提取到仅代表扁平化数据模型的少量文件中,并将数据排列在每个文件中,以便在我们从文件中读取时,它们都以正确的顺序自然流动。

然后我编写了一个Java程序来处理这些展平的数据文件,并为目标系统生成单独的表加载文件。因此,例如,源提取源的源系统中只有不到12个数据文件,这些文件变成了30到40个左右的目标数据库加载文件。

该过程将在几分钟内完成,我整合了审计和错误报告,我们可以快速发现源数据中的问题和差异,修复它们并再次运行处理器。

最后一个难题是我编写的一个多线程实用程序,它在每个加载文件上对目标Oracle数据库执行并行批量加载。该实用程序为每个表创建了一个Java进程,并使用Oracle的批量表加载程序将数据快速推送到Oracle DB中。

当说完并完成时,使用Java和Oracle的批量加载功能组合,将数百万条记录的5天SQL-SQL传输转换为仅30分钟。并且没有错误,我们考虑了在系统之间传输的每个帐户的每一分钱。

所以,也许可以在SQL框之外思考并使用Java,文件系统和Oracle的批量加载器。并确保您在固态硬盘上执行文件IO。

答案 6 :(得分:0)

如果需要从Java处理大型数据库结果集,可以选择JDBC来为您提供所需的低级控制。另一方面,如果您已经在应用程序中使用ORM,那么回退到JDBC可能会带来一些额外的痛苦。在导航域模型时,您将失去乐观锁定,缓存,自动获取等功能。幸运的是,大多数ORM(如Hibernate)都有一些选项来帮助您。虽然这些技术并不新鲜,但有几种可供选择的方式。

一个简化的例子;让我们假设我们有一个表(映射到类&#34; DemoEntity&#34;),有100.000条记录。每个记录包含一个列(映射到属性&#34;属性&#34;在DemoEntity中),其中包含大约约2KB的随机字母数字数据。 JVM使用-Xmx250m运行。让我们假设250MB是可以分配给我们系统上的JVM的总体最大内存。您的工作是读取当前表中的所有记录,执行一些未进一步指定的处理,最后存储结果。我们假设我们的批量操作产生的实体未被修改