2016-07-27 43 views
1

我有使用SqlDataReader的读取数据和产量返回一个IEnumerable的方法,例如:采取(10)与TOP 10与SqlDataReader?

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)方法,因为无论如何,这些额外的数据都会传输到客户端,或者过早地进行优化以避免它(因为yield return代码会在连接之后关闭连接它读取10行,然后数据传输将停止)?

+2

'ORDER BY CreationDate DESC'? – jarlh

+4

这不会是不成熟的优化。从数据库中只取得你实际需要的东西。当你只需要10个客户时,选择10,000个客户是没有意义的。 –

+1

你在误解那篇文章的内容。如果您停止阅读,整个结果集* *不会传输到客户端,尽管某些行可能已被缓冲。 'SqlDataReader'不会超出网络的“预读”。在大多数情况下,仍然希望将'TOP(10)'发送到数据库服务器的原因以及为什么这不是过早的优化,是因为如果优化程序知道您只需要10行而不是读取整个表格(如果没有其他,查询将提前分配更少的内存)。 –

回答

2

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

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

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

关于开销,这不会是我的主要担心,因为这些缓冲区最终有一个固定的大小:服务器不会去缓冲结果集的所有行,无论有多少(这将是非常低效一般来说)。如果你只读了10行,如果你的查询最终返回1,000或1,000,000行,如果它跑到完成不会影响缓冲,但主要是在查询计划方面。尽管如此,它确实增加了开销。

1

你也可以使用分页跳过(0)和采取(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 

C#与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=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)); 
        } 
       } 
      }