我有一个大表,1B +记录,我需要下拉并在每条记录上运行算法。如何使用ADO.NET异步执行“select * from table”并在ado.net接收数据时逐个读取行?
我在读取记录后还需要处理这些记录以节省内存。因此,我正在寻找一种方法来按记录拉下记录表,并基本将记录推入队列进行处理。
我的数据源是oracle和mssql。我必须为几个数据源执行此操作。
答案 0 :(得分:8)
您应该使用SSIS。
您需要了解ADO.Net数据提供商如何工作以了解您可以做什么以及不能做什么的背景细节。让我们以SqlClient
提供商为例。确实可以与BeginExecuteReader
异步执行查询,但这种异步执行只有在查询开始返回结果之后才会执行。在线级,SQL文本被发送到服务器,服务器开始搅拌查询执行,最终将开始将结果行推回客户端。只要第一个数据包返回到客户端,就会完成异步执行并执行完成回调。之后,客户端使用SqlDataReader.Read
()
方法来推进结果集。 SqlDataReader中没有异步方法。这种模式对于在完成一些严肃处理后返回很少结果的复杂查询起了作用。当服务器忙于产生结果时,客户端处于空闲状态而没有阻塞线程。然而,对于生成大型结果集的简单查询(就像您的情况一样),情况完全不同:服务器将立即生成resutl并将继续将它们推回客户端。异步回调几乎是不稳定的,大部分时间将由客户端迭代SqlDataReader来使用。
您说您正在考虑首先将记录放入内存队列中。队列的目的是什么?如果算法处理速度低于DataReader结果集迭代的吞吐量,则此队列将开始构建。它会消耗实时内存,最终会耗尽客户端的内存。为了防止这种情况,你必须建立一个流量控制机制,即。如果队列大小大于N,则不再添加任何记录。但要实现这一点,您必须暂停数据读取器迭代,如果这样做,则将流控制推送到服务器,该服务器将暂停查询,直到通信管道再次可用(直到您开始从读取器读取)。最终,流量控制必须一直延伸到服务器,在任何生产者 - 消费者关系中总是如此,生产者必须停止否则中间队列填满。除了使事情复杂化之外,你的内存中队列完全没有用处。您可以逐个处理读取器中的项目,如果处理速度太慢,数据读取器将导致流控制应用于服务器上运行的查询。这只是因为您没有调用DataReader.Read方法而自动发生。
总而言之,对于大型集合处理,您无法进行异步处理,也不需要队列。
现在是困难的部分。
您的处理是否在数据库中进行任何类型的更新?如果是,那么你有更大的问题:
答案 1 :(得分:1)
DataReader和iterator block(a.k.a。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;
}
}
答案 2 :(得分:0)
一个很好的方法是将块中的数据拉回来,迭代添加到队列中然后再次调用。这比击中每一行的DB要好。如果您通过数字PK将它们拉回来,那么这将很容易,如果您需要按照ROW_NUMBER()的顺序进行订购。
答案 3 :(得分:0)
只需使用DbDataReader(就像damagenoob所说的那样)。它是滚动检索数据的唯一前进方式。您不必处置数据,因为DbDataReader只是转发。
当您使用DbDataReader时,似乎从数据库中逐个检索记录。
但稍微复杂一点:
Oracle(可能还有MySQL)将一次获取几百行,以减少到数据库的往返次数。您可以配置datareader的获取大小。大多数情况下,每次往返是获取100行还是1000行都无关紧要。然而,像1或2行这样的非常低的值会减慢速度,因为检索数据的值很低,需要多次往返。
您可能不必手动设置提取大小,默认情况下也没问题。
edit1:有关Oracle示例,请参阅此处:http://www.oracle.com/technology/oramag/oracle/06-jul/o46odp.html