2012-11-30 69 views
17

因此,我們在工作中爭論着要採用哪種DataAccess路線:DataTable或DataReader。DataTable爲什麼比DataReader更快

免責聲明我在DataReader方面,這些結果動搖了我的世界。

我們寫了一些基準來測試速度差異。人們普遍認爲DataReader速度更快,但我們希望看到更快。

結果令我們驚訝。 DataTable始終比DataReader快。有時接近兩倍。

所以我轉向你,SO的成員。爲什麼當大多數文檔甚至是微軟聲明DataReader速度更快時,我們的測試顯示其他情況。

現在的代碼:

測試工具:

private void button1_Click(object sender, EventArgs e) 
    { 
     System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); 
     sw.Start(); 

     DateTime date = DateTime.Parse("01/01/1900"); 

     for (int i = 1; i < 1000; i++) 
     { 

      using (DataTable aDataTable = ArtifactBusinessModel.BusinessLogic.ArtifactBL.RetrieveDTModified(date)) 
      { 
      } 
     } 
     sw.Stop(); 
     long dataTableTotalSeconds = sw.ElapsedMilliseconds; 

     sw.Restart(); 


     for (int i = 1; i < 1000; i++) 
     { 
      List<ArtifactBusinessModel.Entities.ArtifactString> aList = ArtifactBusinessModel.BusinessLogic.ArtifactBL.RetrieveModified(date); 

     } 

     sw.Stop(); 

     long listTotalSeconds = sw.ElapsedMilliseconds; 

     MessageBox.Show(String.Format("list:{0}, table:{1}", listTotalSeconds, dataTableTotalSeconds)); 
    } 

這是DataReader的的DAL:

 internal static List<ArtifactString> RetrieveByModifiedDate(DateTime modifiedLast) 
     { 
      List<ArtifactString> artifactList = new List<ArtifactString>(); 

      try 
      { 
       using (SqlConnection conn = SecuredResource.GetSqlConnection("Artifacts")) 
       { 
        using (SqlCommand command = new SqlCommand("[cache].[Artifacts_SEL_ByModifiedDate]", conn)) 
        { 
         command.CommandType = CommandType.StoredProcedure; 
         command.Parameters.Add(new SqlParameter("@LastModifiedDate", modifiedLast)); 
         using (SqlDataReader reader = command.ExecuteReader()) 
         { 
          int formNumberOrdinal = reader.GetOrdinal("FormNumber"); 
          int formOwnerOrdinal = reader.GetOrdinal("FormOwner"); 
          int descriptionOrdinal = reader.GetOrdinal("Description"); 
          int descriptionLongOrdinal = reader.GetOrdinal("DescriptionLong"); 
          int thumbnailURLOrdinal = reader.GetOrdinal("ThumbnailURL"); 
          int onlineSampleURLOrdinal = reader.GetOrdinal("OnlineSampleURL"); 
          int lastModifiedMetaDataOrdinal = reader.GetOrdinal("LastModifiedMetaData"); 
          int lastModifiedArtifactFileOrdinal = reader.GetOrdinal("LastModifiedArtifactFile"); 
          int lastModifiedThumbnailOrdinal = reader.GetOrdinal("LastModifiedThumbnail"); 
          int effectiveDateOrdinal = reader.GetOrdinal("EffectiveDate"); 
          int viewabilityOrdinal = reader.GetOrdinal("Viewability"); 
          int formTypeOrdinal = reader.GetOrdinal("FormType"); 
          int inventoryTypeOrdinal = reader.GetOrdinal("InventoryType"); 
          int createDateOrdinal = reader.GetOrdinal("CreateDate"); 

          while (reader.Read()) 
          { 
           ArtifactString artifact = new ArtifactString(); 
           ArtifactDAL.Map(formNumberOrdinal, formOwnerOrdinal, descriptionOrdinal, descriptionLongOrdinal, formTypeOrdinal, inventoryTypeOrdinal, createDateOrdinal, thumbnailURLOrdinal, onlineSampleURLOrdinal, lastModifiedMetaDataOrdinal, lastModifiedArtifactFileOrdinal, lastModifiedThumbnailOrdinal, effectiveDateOrdinal, viewabilityOrdinal, reader, artifact); 
           artifactList.Add(artifact); 
          } 
         } 
        } 
       } 
      } 
      catch (ApplicationException) 
      { 
       throw; 
      } 
      catch (Exception e) 
      { 
       string errMsg = String.Format("Error in ArtifactDAL.RetrieveByModifiedDate. Date: {0}", modifiedLast); 
       Logging.Log(Severity.Error, errMsg, e); 
       throw new ApplicationException(errMsg, e); 
      } 

      return artifactList; 
     } 
    internal static void Map(int? formNumberOrdinal, int? formOwnerOrdinal, int? descriptionOrdinal, int? descriptionLongOrdinal, int? formTypeOrdinal, int? inventoryTypeOrdinal, int? createDateOrdinal, 
     int? thumbnailURLOrdinal, int? onlineSampleURLOrdinal, int? lastModifiedMetaDataOrdinal, int? lastModifiedArtifactFileOrdinal, int? lastModifiedThumbnailOrdinal, 
     int? effectiveDateOrdinal, int? viewabilityOrdinal, IDataReader dr, ArtifactString entity) 
    { 

      entity.FormNumber = dr[formNumberOrdinal.Value].ToString(); 
      entity.FormOwner = dr[formOwnerOrdinal.Value].ToString(); 
      entity.Description = dr[descriptionOrdinal.Value].ToString(); 
      entity.DescriptionLong = dr[descriptionLongOrdinal.Value].ToString(); 
      entity.FormType = dr[formTypeOrdinal.Value].ToString(); 
      entity.InventoryType = dr[inventoryTypeOrdinal.Value].ToString(); 
      entity.CreateDate = DateTime.Parse(dr[createDateOrdinal.Value].ToString()); 
      entity.ThumbnailURL = dr[thumbnailURLOrdinal.Value].ToString(); 
      entity.OnlineSampleURL = dr[onlineSampleURLOrdinal.Value].ToString(); 
      entity.LastModifiedMetaData = dr[lastModifiedMetaDataOrdinal.Value].ToString(); 
      entity.LastModifiedArtifactFile = dr[lastModifiedArtifactFileOrdinal.Value].ToString(); 
      entity.LastModifiedThumbnail = dr[lastModifiedThumbnailOrdinal.Value].ToString(); 
      entity.EffectiveDate = dr[effectiveDateOrdinal.Value].ToString(); 
      entity.Viewability = dr[viewabilityOrdinal.Value].ToString(); 
    } 

這是DAL的DataTable:

 internal static DataTable RetrieveDTByModifiedDate(DateTime modifiedLast) 
     { 
      DataTable dt= new DataTable("Artifacts"); 

      try 
      { 
       using (SqlConnection conn = SecuredResource.GetSqlConnection("Artifacts")) 
       { 
        using (SqlCommand command = new SqlCommand("[cache].[Artifacts_SEL_ByModifiedDate]", conn)) 
        { 
         command.CommandType = CommandType.StoredProcedure; 
         command.Parameters.Add(new SqlParameter("@LastModifiedDate", modifiedLast)); 

         using (SqlDataAdapter da = new SqlDataAdapter(command)) 
         { 
          da.Fill(dt); 
         } 
        } 
       } 
      } 
      catch (ApplicationException) 
      { 
       throw; 
      } 
      catch (Exception e) 
      { 
       string errMsg = String.Format("Error in ArtifactDAL.RetrieveByModifiedDate. Date: {0}", modifiedLast); 
       Logging.Log(Severity.Error, errMsg, e); 
       throw new ApplicationException(errMsg, e); 
      } 

      return dt; 
     } 

結果:

對於10內的測試工具

For 10 iterations within the test harness

迭代對於1000次迭代測試工具

enter image description here

這些結果內的是第二次運行,以減輕差異d用來創建連接。

+3

最終結果不同。一個給你一個DataTable,一個給你一個List 。對於我所知道的DataTable存儲所有未解析的內容,並在讀取時解析它(我實際上不知道DataTables如何在內部存儲它們的數據,我總是懷疑它是XML-ish)。我知道DataTables會浪費內存,而讀者卻不會。 – MatthewMartin

+0

您是在調試器內部還是外部運行比較?調試器通常會以非編譯框架代碼的速度放慢自己的代碼,即使在發佈模式下也是如此。 –

+0

另一件我真的很驚訝的人沒有提到的是,DataTable一次加載一整行,使用GetValues(object []),而你的代碼分別加載每個字段。實際上,每次調用都會產生一些開銷,並且可能開銷足以使DataTable加載速度更快。 –

回答

25

我看到三個問題:

  1. 您使用DataReader的方式通過將其轉換爲列表來消除其大的單項內存優勢,
  2. 您正在環境中運行基準測試,該測試環境與有利於DataTable的生產方式顯着不同,並且
  3. 您花費時間將DataReader記錄轉換爲DataFable代碼中未複製的Artifact對象。

DataReader的主要優點是您無需將所有內容一次加載到內存中。對於Web應用程序中的DataReader來說,這應該是一個巨大的優勢,其中內存(而不是cpu)通常是瓶頸,但是通過將每行添加到通用列表中,您已經否定了這一點。這也意味着,即使您將代碼更改爲一次只使用一條記錄,這些差異也可能不會顯示在您的基準測試中,因爲您在具有大量可用內存的系統上運行它們,這將有利於DataTable。此外,DataReader版本花費時間將結果解析到DataTable尚未完成的工件對象中。

要解決DataReader的使用問題,改變List<ArtifactString>IEnumerable<ArtifactString>無處不在,在你的DataReader DAL改變這一行:

artifactList.Add(artifact); 

這樣:

yield return artifact; 

這意味着你還需要添加將結果迭代到DataReader測試工具以保持公平性的代碼。

我不確定如何調整基準以創建對DataTable和DataReader均公平的更典型場景,除非要構建兩個版本的頁面,並且在類似的生產環境下爲每個版本提供一個小時水平的負載,以便我們有真正的內存壓力......做一些真正的A/B測試。另外,確保你將DataTable行轉換爲Artifacts ...並且如果參數是你需要爲DataReader而不是DataTable執行此操作,那只是錯誤的。

+0

我不同意這是「使用DataReader錯誤」。使用DataReader返回實體列表的DAL非常常見。懶惰的枚舉有它的地方 - 例如如果BLL正在計算聚合物,但它不是唯一的方法去皮膚貓。 – Joe

+1

@Joe - 也許這不是「錯誤」,但如果這是你的「正確」,你已經否定了大部分正常的數據讀取器優點,並且有更好的方法來編寫你的DAL。但是,我已經重述了這一點。 –

+0

+1非常有趣的點Joel,謝謝。您能否再擴展一下_「正常的數據讀取器優勢」_和_「更好的編寫DAL的方法」_? –

0

我不認爲這將佔到所有的差異,但嘗試這樣的事情,以消除一些額外變量和函數調用:

using (SqlDataReader reader = command.ExecuteReader()) 
{ 
    while (reader.Read()) 
    { 
     artifactList.Add(new ArtifactString 
     { 
      FormNumber = reader["FormNumber"].ToString(), 
      //etc 
     }); 
    } 
} 
2

SqlDataAdapter.Fill調用SqlCommand.ExecuteReader與CommandBehavior.SequentialAccess設置。也許這足以造成差異。

另外,我看到您的IDbReader實現緩存每個字段的序號出於性能原因。此方法的替代方法是使用DbEnumerator類。

DbEnumerator緩存字段名稱 - >序字典內,所以給你太多的使用序使用的字段名稱的簡單的性能優勢:

foreach(IDataRecord record in new DbEnumerator(reader)) 
{ 
    artifactList.Add(new ArtifactString() { 
     FormNumber = (int) record["FormNumber"], 
     FormOwner = (int) record["FormOwner"], 
     ... 
    }); 
} 

甚至:

return new DbEnumerator(reader) 
    .Select(record => new ArtifactString() { 
     FormNumber = (int) record["FormNumber"], 
     FormOwner = (int) record["FormOwner"], 
     ... 
     }) 
    .ToList(); 
+0

+1這並沒有涵蓋整個區別,但它縮短了一些時間。謝謝。 –

+0

我覺得這個更具可讀性的'foreach(IDataRecord in(DbDataReader)reader)',意思更清晰。 – nawfal

2

2件事可能會讓你放慢腳步。

首先,如果您對錶演感興趣,我不會爲每個專欄「按名稱查找序號」。 請注意,下面的「佈局」類負責此查找。 而且佈局提供者後來的可讀性,而不是使用「0」,「1」,「2」等。 它允許我編碼到一個接口(IDataReader),而不是混凝土。

二。您正在使用「.Value」屬性。 (我認爲這確實有所幫助)

如果您使用具體的數據類型「getters」,您將獲得更好的結果(恕我直言)。

GetString, GetDateTime, GetInt32, 等等。

這是我典型的IDTOReader to DTO/POCO代碼。

[Serializable] 
public partial class Employee 
{ 
    public int EmployeeKey { get; set; }     
    public string LastName { get; set; }     
    public string FirstName { get; set; } 
    public DateTime HireDate { get; set; } 
} 

[Serializable] 
public class EmployeeCollection : List<Employee> 
{ 
} 

internal static class EmployeeSearchResultsLayouts 
{ 
    public static readonly int EMPLOYEE_KEY = 0; 
    public static readonly int LAST_NAME = 1; 
    public static readonly int FIRST_NAME = 2; 
    public static readonly int HIRE_DATE = 3; 
} 


    public EmployeeCollection SerializeEmployeeSearchForCollection(IDataReader dataReader) 
    { 
     Employee item = new Employee(); 
     EmployeeCollection returnCollection = new EmployeeCollection(); 
     try 
     { 

      int fc = dataReader.FieldCount;//just an FYI value 

      int counter = 0;//just an fyi of the number of rows 

      while (dataReader.Read()) 
      { 

       if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.EMPLOYEE_KEY))) 
       { 
        item = new Employee() { EmployeeKey = dataReader.GetInt32(EmployeeSearchResultsLayouts.EMPLOYEE_KEY) }; 

        if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.LAST_NAME))) 
        { 
         item.LastName = dataReader.GetString(EmployeeSearchResultsLayouts.LAST_NAME); 
        } 

        if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.FIRST_NAME))) 
        { 
         item.FirstName = dataReader.GetString(EmployeeSearchResultsLayouts.FIRST_NAME); 
        } 

        if (!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.HIRE_DATE))) 
        { 
         item.HireDate = dataReader.GetDateTime(EmployeeSearchResultsLayouts.HIRE_DATE); 
        } 


        returnCollection.Add(item); 
       } 

       counter++; 
      } 

      return returnCollection; 

     } 
     //no catch here... see http://blogs.msdn.com/brada/archive/2004/12/03/274718.aspx 
     finally 
     { 
      if (!((dataReader == null))) 
      { 
       try 
       { 
        dataReader.Close(); 
       } 
       catch 
       { 
       } 
      } 
     } 
    } 
+0

GetValue()的+1。我同意,並且我不能在我的生活中找出我爲什麼這樣做。 :)。雖然,我並不完全同意你的陳述「按名稱尋找序號」。由於每次通話只進行一次,所以影響很小。事實上,我曾經做過一次測試,根據序號和姓名的區別最好是疏忽大意。 –

+0

是的,我確實看到你做了「只得一次序」,這很好。我只是想盡量調整每一點。隨着「佈局」,我獲得了可讀性。如果職位發生變化,我只有一個地方可以更新他們。我猜可能是腳趾,腳趾腳趾。 – granadaCoder

相關問題