.net 为什么DataTable比DataReader快

.net 为什么DataTable比DataReader快,.net,data-access-layer,sqldatareader,.net,Data Access Layer,Sqldatareader,因此,我们在工作中就采用哪种数据访问路径展开了激烈的辩论:DataTable还是DataReader 免责声明我站在DataReader一边,这些结果震撼了我的世界 我们最后编写了一些基准测试来测试速度差异。人们普遍认为DataReader的速度更快,但我们想看看它的速度有多快 结果令我们惊讶。DataTable始终比DataReader快。有时接近两倍的速度 因此,我转向各位,So的成员。为什么,当大多数文档甚至是Microsoft都声明DataReader更快时,我们的测试却显示出了相反的结

因此,我们在工作中就采用哪种数据访问路径展开了激烈的辩论:DataTable还是DataReader

免责声明我站在DataReader一边,这些结果震撼了我的世界

我们最后编写了一些基准测试来测试速度差异。人们普遍认为DataReader的速度更快,但我们想看看它的速度有多快

结果令我们惊讶。DataTable始终比DataReader快。有时接近两倍的速度

因此,我转向各位,So的成员。为什么,当大多数文档甚至是Microsoft都声明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));
    }
        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();
    }
结果:

    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));
    }
        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();
    }
在测试线束中进行10次迭代

在测试线束中进行1000次迭代


这些结果是第二次运行,以减轻由于创建连接而产生的差异。

我认为这不会解释所有差异,但请尝试以下方法以消除一些额外的变量和函数调用:

using (SqlDataReader reader = command.ExecuteReader())
{
    while (reader.Read())
    {
        artifactList.Add(new ArtifactString
        {
            FormNumber = reader["FormNumber"].ToString(),
            //etc
        });
     }
}
我看到三个问题:

  • 您使用DataReader的方式通过将其转换为列表否定了它在内存中的单个大项优势
  • 您在一个环境中运行基准测试,该环境在某种程度上与生产环境有很大不同,有利于数据表,并且
  • 您将花费时间将DataReader记录转换为DataTable代码中不重复的工件对象
  • DataReader的主要优点是不必一次将所有内容加载到内存中。这对于web应用程序中的DataReader来说应该是一个巨大的优势,在web应用程序中,内存而不是cpu通常是瓶颈,但是通过将每一行添加到一个通用列表中,您已经否定了这一点。这也意味着,即使您将代码更改为一次只使用一条记录,差异也可能不会显示在基准测试上,因为您在具有大量可用内存的系统上运行基准测试,这将有利于数据表。此外,DataReader版本正在花费时间将结果解析为DataTable尚未完成的工件对象

    要解决DataReader使用问题,请将
    List
    更改为
    IEnumerable
    随处可见,并在DataReader DAL中更改此行:

    artifactList.Add(artifact);
    
    为此:

    yield return artifact;
    
    这意味着您还需要将迭代结果的代码添加到DataReader测试工具中,以保持公平


    我不知道如何调整基准以创建对DataTable和DataReader都公平的更典型的场景,除了构建两个版本的页面,并在类似的生产级别负载下为每个版本提供一个小时的服务,以便我们有真正的内存压力。。。做一些真正的A/B测试。另外,请确保涵盖将DataTable行转换为工件的过程。。。如果参数是您需要为DataReader执行此操作,而不是为DataTable执行此操作,那么这显然是错误的。

    SqlDataAdapter.Fill
    使用
    CommandBehavior.SequentialAccess
    设置调用SqlCommand.ExecuteReader。也许这就足够改变了

    另一方面,我看到您的
    IDbReader
    实现出于性能原因缓存了每个字段的序号。这种方法的另一种选择是使用类

    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();
    

    有两件事可能会让你慢下来

    首先,如果您对性能感兴趣,我不会为每个列执行“按名称查找序号”。 注意,下面的“layout”类负责此查找。 而布局提供程序以后的可读性,而不是使用“0”、“1”、“2”等。 它允许我编写一个接口(IDataReader)而不是具体的代码

    第二。您正在使用“.Value”属性。(我认为这确实会有所不同)

    如果使用具体的数据类型“getters”,您将获得更好的结果(IMHO)

    GetString, GetDateTime, GetInt32, 等等等等

    这是我典型的DTO/POCO代码的IDataReader

    [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
                    {
                    }
                }
            }
        }
    
    [可序列化]
    公共部分类雇员
    {
    public int EmployeeKey{get;set;}
    公共字符串LastName{get;set;}
    公共字符串名{get;set;}
    公共日期时间HireDate{get;set;}
    }
    [可序列化]
    公共类EmployeeCollection:列表
    {
    }   
    内部静态类EmployeeSearchResultsLayouts
    {
    public static readonly int EMPLOYEE_KEY=0;
    公共静态只读int LAST_NAME=1;
    公共静态只读int FIRST_NAME=2;
    公共静态只读int HIRE_DATE=3;
    }
    公共EmployeeCollection序列化EmployeeSearchForCollection(IDataReader数据读取器)
    {
    员工项目=新员工();
    EmployeeCollection returnCollection=新EmployeeCollection();
    尝试
    {
    int fc=dataReader.FieldCount;//只是一个仅供参考的值
    int counter=0;//只是行数的参考值
    while(dataReader.Read())
    {
    if(!(dataReader.IsDBNull(EmployeeSearchResultsLayouts.EMPLOYEE_KEY)))
    {
    item=newemployee(){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);
    }
    如果(!(dataReader.IsDBNull(EmployeeSearchR