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