【问题标题】:ADO.NET asynchronous reader (queue processing)ADO.NET 异步读取器(队列处理)
【发布时间】:2009-09-04 15:35:37
【问题描述】:

我有一个大表,有 1B+ 条记录,我需要拉下并在每条记录上运行一个算法。如何使用 ADO.NET 异步执行“从表中选择 *”并在 ado.net 接收数据时开始一一读取行?

我还需要在阅读记录后将其处理掉以节省内存。所以我正在寻找一种方法来逐条记录地拉下表格,然后将记录推入队列进行处理。

我的数据源是 oracle 和 mssql。我必须为多个数据源执行此操作。

【问题讨论】:

    标签: sql-server oracle ado.net


    【解决方案1】:

    您应该为此使用SSIS

    您需要一些关于 ADO.Net 数据提供者如何工作的背景细节,以了解您可以做什么和不能做什么。让我们以SqlClient 提供者为例。确实可以使用BeginExecuteReader 异步执行查询,但这种异步执行仅在查询开始返回结果之前执行。在线路级别,SQL 文本被发送到服务器,服务器开始搅动查询执行,并最终开始将结果行推回客户端。一旦第一个数据包返回到客户端,异步执行完成并执行完成回调。之后,客户端使用SqlDataReader.Read() 方法推进结果集。 SqlDataReader 中没有异步方法。这种模式非常适合在完成一些严肃处理后返回很少结果的复杂查询。当服务器忙于产生结果时,客户端处于空闲状态,没有线程阻塞。然而,对于产生大型结果集的简单查询(您似乎就是这种情况),情况完全不同:服务器将立即产生结果,并将继续将它们推回客户端。异步回调几乎是即时的,并且大部分时间将花费在客户端迭代 SqlDataReader 上。

    您说您正在考虑首先将记录放入内存队列中。排队的目的是什么?如果您的算法处理比 DataReader 结果集迭代的吞吐量慢,则此队列将开始建立。它将消耗实时内存并最终耗尽客户端上的内存。为了防止这种情况,你必须建立一个流量控制机制,即。如果队列大小大于 N,则不要再将任何记录放入其中。但要实现这一点,您必须暂停数据读取器迭代,如果您这样做,您将流控制推送到服务器,服务器将暂停查询,直到通信管道再次可用(直到您开始从读取器读取)。最终,流控制必须一直传播到服务器,这在任何生产者-消费者关系中总是如此,生产者必须停止,否则中间队列会填满。除了使事情复杂化之外,您的内存队列根本没有任何用途。您可以简单地逐个处理来自阅读器的项目,如果您的处理速度太慢,数据阅读器将导致对服务器上运行的查询应用流量控制。这会自动发生,因为您没有调用 DataReader.Read 方法。

    总而言之,对于大型集合处理,您不能进行异步处理,也不需要队列。

    现在是困难的部分。

    您的处理是否在数据库中进行任何类型的更新?如果是,那么你有更大的问题:

    • 您不能使用相同的连接来写回结果,因为它正忙于数据读取器。 SqlClient for SQL Server 支持MARS,但这只能解决 SQL 2005/2008 的问题。
    • 如果您要在一个事务中注册读取和更新,如果您的更新发生在不同的连接上(见上文),那么这意味着使用分布式事务(即使涉及的两个连接指向同一个服务器) .分布式事务很慢。
    • 您需要将处理分成几个批次,因为在单个事务中处理 1B+ 记录非常糟糕。这也意味着您必须能够恢复处理已中止的批次,这意味着您必须能够识别已处理的记录(除非处理是幂等的)。

    【讨论】:

      【解决方案2】:

      DataReaderiterator block(又名generator)的组合应该非常适合解决此问题。 Microsoft 提供的默认 DataReader 一次从数据源中提取数据 one record

      这是一个 C# 示例:

      static IEnumerable<User> RetrieveUsers(DbDataReader reader)
      {
          while (reader.NextResult())
          {
              User user = new User
                              {
                                  Name = reader.GetString(0),
                                  Surname = reader.GetString(1)
                              };
              yield return user;
          }
      } 
      

      【讨论】:

      • 我认为 DbDataReaders 不会一次从数据库中提取一条记录。那将需要太多的往返行程。默认情况下,它们一次获取几百行(除非记录非常大)。
      • 添加了说明默认实现一次检索一行的参考。
      • OracleDataReader 默认获取 64 kb 的块,因此除非记录非常大,否则它将检索超过 1 行。在这里阅读:oracle.com/technology/oramag/oracle/06-jul/o46odp.html
      • 你说得对,我忘了 DataReaders 可以由 Microsoft 以外的供应商提供。让我的断言更加清晰。
      【解决方案3】:

      解决此问题的一个好方法是按块拉回数据,迭代添加到您的队列,然后再次调用。这将比为每一行击中数据库要好。如果您通过数字 PK 将它们拉回来,那么这将很容易,如果您需要按某些东西订购,您可以使用 ROW_NUMBER() 来执行此操作。

      【讨论】:

        【解决方案4】:

        只需使用 DbDataReader(就像损坏的noob 所说)。这是滚动检索数据的唯一方式。您不必处理您的数据,因为 DbDataReader 只是转发的。

        当您使用 DbDataReader 时,似乎记录是从数据库中一一检索的。

        不过稍微复杂一点:

        Oracle(可能还有 MySQL)一次会提取 100 行数据,以减少到数据库的往返次数。您可以配置数据读取器的读取大小。大多数情况下,每次往返获取 100 行还是 1000 行都无关紧要。然而,像 1 或 2 行这样的非常低的值会减慢速度,因为使用低值检索数据将需要多次往返。

        您可能不必手动设置提取大小,默认值就可以了。

        edit1:查看这里的 Oracle 示例:http://www.oracle.com/technology/oramag/oracle/06-jul/o46odp.html

        【讨论】:

          猜你喜欢
          • 2013-09-10
          • 1970-01-01
          • 1970-01-01
          • 2019-11-05
          • 2023-03-24
          • 1970-01-01
          • 2022-11-25
          • 1970-01-01
          • 2017-09-30
          相关资源
          最近更新 更多