2016-07-27 60 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)); 
        } 
       } 
      }