【问题标题】:Take(10) vs TOP 10 With SqlDataReader?Take(10) vs TOP 10 使用 SqlDataReader?
【发布时间】:2016-07-27 09:25:33
【问题描述】:

我有一个使用 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 传输到客户端 - 我应该避免使用Take(10) 方法吗?无论如何都会向客户端传输额外的数据,或者避免它将是一个过早的优化(因为在读取 10 行后,yield 返回代码会关闭连接,然后数据传输无论如何都会停止)?

【问题讨论】:

  • ORDER BY CreationDate DESC?
  • 这不会是过早的优化。仅从数据库中获取您实际需要的内容。当您只需要 10 个客户时,选择 10,000 个客户是没有意义的。
  • 您误解了该帖子的内容。如果您停止读取,则整个结果集不会传输到客户端,尽管某些行可能已经被缓冲。 SqlDataReader 不会在网络之外“预读”。在大多数情况下,您仍希望将TOP(10) 发送到数据库服务器的原因,以及为什么这不是过早的优化,是因为如果优化器知道您只需要 10 行而不是读取整个行,它可以生成更有效的计划表(如果没有别的,查询会提前分配更少的内存)。
  • 另一种思考方式:过早的优化是当您开始更改正确表达您想要的代码以尝试使其更快时,甚至在确定它是必要的之前。使用SELECT 而不使用TOP,即使您只需要有限数量的行不是从数据库的角度正确表达您想要的内容。这在 SQL 中是一件大事,因为您的声明是服务器用来生成查询计划的唯一东西。极端情况下,这意味着您的所有查询都变为SELECT * FROM [Table]
  • 理论上可以在客户端缓存整个结果集,是的。但这需要相当大的网络缓冲区——您的平均数据包不能容纳超过平均表的几行。超过 10 个,可能,但不是 10,000 个。没有无限的预读/缓冲。 SqlDataReader 不是 DataTable - 在您请求之前不会实际读取任何行,只是第一行也会拉下一个 X。确实,这也导致了效率低下,因为服务器仍在检索并发送这些行。

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


【解决方案1】:

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

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

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

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

【讨论】:

    【解决方案2】:

    您还可以使使用分页 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
    

    带有 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));
                        }
                    }
                }
    

    【讨论】:

      猜你喜欢
      • 2018-07-17
      • 2014-08-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-30
      相关资源
      最近更新 更多