使用SqlDataReader获取(10)vs TOP 10?

时间:2016-07-27 09:25:33

标签: c# sql sqldatareader yield-return premature-optimization

我有一个使用SqlDataReader读取数据的方法,yield返回一个IEnumerable,例如:

IEnumerable<string> LoadCustomers()
{
 using(SqlDataReader rdr = cmd.ExecuteReader())
 {
    while (rdr.Read())
    {
        yield return rdr.GetString(0); 
    }
 }
}

现在让我们假设我只需要最新的10位客户。我能做到

LoadCustomers.Take(10)

或将10作为参数传递给sql并制作我的sql

SELECT TOP 10 FROM Customers ORDER BY CreationDate DESC

根据this post,整个结果集从sql server传输到客户端,即使datareader只读取几行(只要连接打开) - 我应该避免使用Take(10)方法是因为无论如何都要向客户端发送额外的数据,或者为了避免它而过早优化(因为收益返回码会在读取10行之后关闭连接然后数据传输会停止)?

2 个答案:

答案 0 :(得分:2)

由于优化是否“过早”是主观的,我选择将此问题解释为“使用DataReader并在10行具有与使用TOP(10)相同的性能特征后停止读取在查询?“

答案是否定的。将TOP(10)传递给服务器允许优化器调整读取,内存授予,I / O缓冲区,锁粒度和并行性,并且知道查询将返回(在这种情况下,也读取)最多10行。抛弃TOP意味着它必须为客户端将要读取所有行的情况做好准备 - 无论您是否真的提前停止。

无论您是否阅读过,服务器都不会发送行。使用SqlDataReader拉取行在概念上是逐行操作:当您发出Reader.MoveNext时,从服务器获取下一行并且只获取该行。但为了提高性能,在请求行之前会对行进行缓冲(在服务器端和行网络缓冲区中都是如此)。因此,在第一次.MoveNext调用之后,有可能最终在缓冲区中检索到100行,即使您只阅读其中的10行。

关于开销,这不是我主要关注的问题,因为这些缓冲区最终具有固定大小:服务器不会去缓冲结果集的所有行,无论有多少行(这将是非常低效的)一般)。如果你只读了10行,那么如果你的查询在运行完成时最终会返回1000或1,000,000行,那么在缓冲方面并不重要,但主要是在查询计划方面。然而,它确实增加了开销。

答案 1 :(得分:1)

你也可以使用分页Skip(0)和Take(10)更灵活。

SQL SERVER 2012

SELECT name,
       CreationDate        
  FROM customer
 ORDER BY
       CreationDate      
OFFSET @skip ROWS
FETCH NEXT @take ROWS ONLY;

SQL 2005至2008

SET @take = (@skip + @take)

;WITH customer_page_cte AS
(SELECT 
        name, 
        CreationDate,
        ROW_NUMBER() OVER (ORDER BY CreationDate desc) AS RowNumber
 FROM customer
)

SELECT name, 
       CreationDate
  FROM customer_page_cte
 WHERE RowNumber > @skip AND RowNumber <= @take

使用sql 2012的C# - 使用存储过程进行命令:)

var command = @"SELECT name,
                       CreationDate        
                  FROM customer
                 ORDER BY
                       CreationDate      
                 OFFSET @skip ROWS
                 FETCH NEXT @take ROWS ONLY;";

            using (var conn = new SqlConnection("Data Source=.;Initial Catalog=Stackoverflow;Integrated Security=True"))
            {
                conn.Open();
                using (var cmd = new SqlCommand(command, conn))
                {
                    cmd.Parameters.AddWithValue("skip", 0);
                    cmd.Parameters.AddWithValue("take", 10);

                    var reader = cmd.ExecuteReader();
                    while (reader.Read())
                    {
                        Console.WriteLine(reader.GetString(0));
                    }
                }
            }