Take(10) vs TOP 10 使用 SqlDataReader?

Take(10) vs TOP 10 With SqlDataReader?

我有一个方法可以使用 SqlDataReader 读取数据并生成 returns 一个 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 服务器传输到客户端,即使数据读取器只读取几行(只要连接打开) - 我应该避免Take(10) 方法,因为无论如何都会将额外的数据传输到客户端,或者避免它是过早的优化(因为 yield return 代码会在读取 10 行然后读取数据后关闭连接无论如何传输都会停止)?

您还可以使用分页 Skip(0) 和 Take(10) 更加灵活。

SQL 服务器 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

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

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=Whosebug;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));
                    }
                }
            }

由于是否优化"premature"是主观的,所以我选择将此问题解释为"does using a DataReader and stopping the read after 10 rows have the same performance characteristics as using TOP(10) in the query?"

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

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

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