SQLDataReader如何处理非常大的查询
本文关键字:非常 查询 处理 何处理 SQLDataReader | 更新日期: 2023-09-27 18:01:46
实际上我不确定标题是否准确地描述了这个问题,但我希望它足够接近。
我有一些代码,从数据库表执行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在这里做什么呢?SELECT的结果是否被存放在某个地方,以便SQLDataReader在每次Read()移动指针时蚕食一行?还是返回到数据库?
我问的原因是……处理"这里的客户号码"部分代码可能需要一些时间,因此对于150万客户,该代码(上面的while循环)将需要许多小时才能完成。当这种情况发生时,我是否需要担心其他人在数据库上阻塞在我身后,或者我是否安全,因为我已经从数据库中完成了一次SELECT,并且我不会再回去了?
select将作为"单个、整体事务"执行。输出的余额缓存在SQL Server中,并在协议确定有可用的缓冲区来接收它时传递给网络。但是,SQL Server不会每次都回到数据表中。原始SELECT
经过时的数据状态将返回给您的应用程序。如果指定了(NOLOCK),则不会对数据产生进一步的影响。其他人可以阅读&写;您将看不到它们的更改。你还没有完成SQL Server,然而,直到最后一行是在你的应用程序服务器的缓冲区,几个小时后。每次"我现在有更多的空间,请"都会有网络流量,但不会明显超过整个30MB的流量。
请求只发送一次,而不是每次都发送。然后,结果将根据大小由几个结果集发送回客户端。
默认结果集是向客户端传输结果的最有效方式。从客户机计算机发送到服务器的唯一数据包是原始数据包,其中包含要执行的语句。当结果被发送回客户端时,SQL Server将尽可能多的结果集行放入每个数据包中,从而最小化发送到客户端的数据包数量。
Ref http://msdn.microsoft.com/en-us/library/ms187602.aspx
当一个请求被提交执行时,SQL Server以以下方式将结果集发送回客户端:
- SQL Server从客户端收到一个包含Transact-SQL语句或一批Transact-SQL语句执行。
- SQL Server编译并执行语句或批处理。
- SQL Server开始放入结果集的行,或多个来自批处理或存储过程的结果集,在网络数据包和将它们发送给客户端。SQL Server放置尽可能多的结果集行
- 包含结果集行的报文缓存在网络中客户端的缓冲区。当客户端应用程序获取行时,ODBC驱动程序或OLE DB提供程序从网络缓冲数据并将其传输到客户端应用程序。客户端以转发方式一次一行地检索结果方向。
在一个大块中不给应用程序提供默认结果集。结果集缓存在客户端的网络缓冲区中。应用程序一次获取一行结果集。在每次获取时,OLE DB提供程序或ODBC驱动程序将数据从网络缓冲区的下一行移动到应用程序中的变量中。OLE DB、ODBC和ADO应用程序使用相同的API函数来检索用于从游标中获取行的行。SqlClient托管提供程序使用SqlDataReader类公开默认结果集。当MultipleActiveResultSets设置为true时,允许在给定时间打开多个SqlDataReader。
Ref: http://technet.microsoft.com/en-us/library/ms187602(v=sql.105).aspx
首先,我将重定向到以下关于SO的问题,其中描述了如何处理锁等:
了解SQL Server在SELECT查询上的锁
我的第一个问题是,您将运行这个查询多少次?如果是按日计算,请确保选择一个使用数据库的用户最少的时间。
第二个问题是,你打算如何处理这些数据?也许您应该记住,当处理1M以上的记录时,存储过程将更快,因为它处理数据库上的所有内容,并将保持低流量。
DataReader
在客户端没有缓存任何内容;每次调用Read()
时,它都会尝试从服务器流式传输数据。(根据经验)是这样的:
-
ExecuteReader()
阻塞,直到第一个 Sql语句产生数据返回给客户端。 -
NextResult()
阻塞直到服务器- 表示服务器和"命令"
- XOR命令中的后续语句产生数据返回给客户端。
-
Read()
阻塞,直到服务器能够将下一条记录流式传输到客户端。(是的,这意味着一个无序的选择几乎总是比一个有序的选择更早开始流到客户端。)- 。我看到SqlServer花15秒开始流结果(
NextResult()
返回),然后,过了一会儿,阻塞15秒调用Read()
;这是一个SELECT和一个ORDER BY。(与从SSMS执行查询时的行为相同)
- 。我看到SqlServer花15秒开始流结果(
- 整个命令文本将与
DataReader
的操作同步执行。- 。如果您的命令中有2个SELECT语句,并且都返回数据,那么第二个SELECT将只在调用
NextResult()
时才开始在服务器上执行。但是如果第一个返回零结果,第二个将在ExecuteReader()
期间开始执行。(在任何情况下,你总是需要调用NextResult()
来获得第二个SELECT的数据。)
- 。如果您的命令中有2个SELECT语句,并且都返回数据,那么第二个SELECT将只在调用
仅供参考:我的经验是(MS) Sql2019和。net框架,IIRC这种行为在2013年仍然是真实的。
那么明确地回答你的问题
你的查询是一个单独的SELECT语句,它将在自己的隐式事务中执行。一旦服务器完成查找所有要返回的数据/行,它将释放它可能在表上获得的所有锁,此时,您的代码将不会对访问这些相同表的其他查询产生任何直接影响。
但是,在完成所有Read()
调用之前,您仍然在占用服务器上的资源,并从连接池中命令对该连接的独占访问。因此,在您的示例中,您希望更改while(reader.Read())
循环以将所有数据捕获到本地对象中。然后在关闭连接后编写一个后续循环,对该数据执行长时间运行的流程。