SQLDataReader如何处理真正大的查询?

时间:2014-05-05 07:51:12

标签: c# sql

实际上我不确定标题是否准确描述了这个问题,但我希望它足够接近。

我有一些代码从数据库表执行SELECT,我知道这将导致大约150万行被选中。每行中的数据不大 - 每行可能20个字节。但这仍然是30MB的数据。每行包含一个客户编号,我需要为每个客户做一些事情。

我的代码类似于:

SqlConnection conn = new SqlConnection(connString);
SqlCommand command = new SqlCommand("SELECT ... my select goes here", conn);
using (conn)
{
    conn.Open();
    using (SqlDataReader reader = command.ExecuteReader())
    {
        while(reader.Read())
        {
            ... process the customer number here
        }
    }
}

所以我只是迭代SELECT返回的所有客户。

我的问题是,这会导致多次读取数据库,还是只读取一次?我假设网络缓冲区不足以容纳30MB的数据,那么.NET在这里做什么呢?每次Read()推进指针时,SELECT的结果是否会让某些地方的SQLDataReader啃掉一行?或者它会回到数据库吗?

我之所以要问的是,“在这里处理客户编号”部分代码可能需要一些时间,因此对于150万客户而言,代码(上面的while循环)将花费很多时间来完成。在发生这种情况的时候,我是否需要担心其他人在数据库上阻挡我,或者我知道我已经从数据库中完成了一个SELECT并且我不会再回来了吗?

4 个答案:

答案 0 :(得分:4)

select将作为“单一,单片事务”执行。输出的余额缓存在SQL Server中,并在协议确定有可用于接收它的缓冲区时传递给网络。但是,每次SQL Server都不会返回数据表。原始SELECT传递给它的数据的状态将返回给您的应用程序。如果指定了(NOLOCK),则不会对数据产生进一步影响。其他人可以阅读&写下来;你不会看到他们的变化。但是,您还没有完成SQL Server,直到最后一行位于您的应用服务器的缓冲区中,数小时后。每个“我有更多的空间,现在请更多网络流量”,但不会明显超过整个30MB的所有内容。

使用大型结果集和长时间运行的流程,您最好将应用程序编写为批量处理数据,即使基础结构可以支持完整的查询输出。回答每个批处理查询需要的资源更少。在失败的情况下,您只需要处理剩余的行;你不必从头开始。您的应用程序最终将完成更多的工作,但每个块对环境的破坏性会更小。

答案 1 :(得分:3)

请求发送一次,而不是每次您的读者前进。然后,结果将根据大小通过多个结果集发送回客户端。

  

默认结果集是将结果传输到客户端的最有效方式。从客户端计算机发送到服务器的唯一数据包是包含要执行的语句的原始数据包。当结果发送回客户端时,SQL Server会将尽可能多的结果集行放入每个数据包中,从而最大限度地减少发送到客户端的数据包数量。

参考http://msdn.microsoft.com/en-us/library/ms187602.aspx

  

当提交请求以供执行时,SQL Server以下列方式将结果集发送回客户端:

  1. SQL Server从包含该客户端的客户端接收网络数据包 Transact-SQL语句或批处理的Transact-SQL语句 执行。
  2. SQL Server编译并执行语句或批处理。
  3. SQL Server开始放置结果集的行或多个行 来自批处理或存储过程的结果集,网络数据包和 将它们发送给客户端。 SQL Server放置尽可能多的结果集行 尽可能在每个数据包中。
  4. 包含结果集行的数据包缓存在网络中 客户端的缓冲区。当客户端应用程序获取行时, ODBC驱动程序或OLE DB提供程序从中提取行 网络缓冲并将数据传输到客户端应用程序。 客户端在转发中一次检索一行结果 方向。
  5.   

    未在一个大块中为应用程序提供默认结果集。结果集缓存在客户端的网络缓冲区中。应用程序一次通过结果集获取一行。在每次获取时,OLE DB提供程序或ODBC驱动程序将数据从网络缓冲区中的下一行移动到应用程序中的变量中。 OLE DB,ODBC和ADO应用程序使用相同的API函数来检索它们用于从游标中获取行的行。 SqlClient托管提供程序使用SqlDataReader类来公开默认结果集。当MultipleActiveResultSets设置为true时,允许在给定时间打开多个SqlDataReader。

    参考:http://technet.microsoft.com/en-us/library/ms187602(v=sql.105).aspx

答案 2 :(得分:1)

首先,我要将您重定向到SO上的以下问题,其中描述了锁等的处理方式:

Understanding SQL Server LOCKS on SELECT queries

我的第一个问题是,您将运行此查询多少次。如果它是每日金额,请确保选择用户数量最少的时间。

第二个问题是,您要对数据做什么?也许您应该记住,在处理1M +记录时,存储过程会更快,因为它处理数据库上的所有内容并且会使流量保持较低。

答案 3 :(得分:0)

DataReader不会在客户端上缓存任何内容;每次您调用Read()时,它都会尝试从服务器流式传输数据。 (根据经验)是这样的:

  1. ExecuteReader()一直阻塞,直到第一个 Sql语句产生数据以返回客户端为止。
  2. NextResult()一直阻塞到服务器或者
    1. 表示服务器上没有执行任何其他操作,并且“命令”实际上已完成。
    2. 对命令中的后续语句进行XOR生成数据以返回到客户端。
  3. Read()阻塞,直到服务器能够将下一条记录流式传输到客户端。 (是的,这意味着无序选择几乎总是比有序选择更快地开始流式传输到客户端。)
    • 例如我已经看到SqlServer需要15秒才能开始流式传输结果(返回NextResult()),然后过一会儿,在调用Read()时又阻塞了15秒钟;这是在带有ORDER BY的SELECT上进行的。 (从SSMS执行查询时,行为相同。)
  4. 整个命令文本将与DataReader的操作方式同步执行。
    • 即如果命令中有2条SELECT语句均返回数据,则第二条SELECT仅在调用NextResult()时才开始在服务器上执行。但是,如果第一个返回零结果,则第二个将在ExecuteReader()期间开始执行。 (无论如何,您总是需要调用NextResult()来获取第二个SELECT的数据。)

仅供参考:我的经验是使用(MS)Sql2019和.Net Framework,并且IIRC的这种行为在2013年仍然是正确的。

所以要明确回答您的问题

您的查询是单个SELECT语句,它将在其自身的隐式事务中执行。服务器完成查找要返回的所有数据/行后,它将释放它可能已在表上获取的所有锁,并且此时,您的代码不会对其他查询产生任何直接影响相同的表。

但是,直到完成所有Read()调用之后,您仍在占用服务器上的资源,并从连接池中命令对该连接的独占访问。因此,在您的示例中,您想更改while(reader.Read())循环以仅将所有数据捕获到本地对象中。关闭连接后,然后编写后续循环,以对这些数据进行长时间运行的过程。