InfoQ

新闻

挖掘ADO.NET Entity框架的性能

作者 Robert Bazinet 发布于 2008年2月23日 上午8时14分

社区
.NET
主题
数据访问,
.NET框架
标签
ADO.NET实体框架,
微软,
ADO.NET

ADO.NET团队最近讨论了ADO.NET Entity框架的各种性能特征。ADO.NET Entity框架在12月已经进入它的第三个beta版本,自那时起开发团队就开始为开发人员提供了使用该框架的相关信息。而现在,则为开发人员提供了框架性能方面的信息。

本文鞭辟入里地介绍了ADO.NET Entity框架的性能,演示了如何提高简单查询速度的方法,并阐释了框架的性能特征。

需要重点指出的是,当一个抽象层或者类似EDM(译注:指Entity Data Model)的模块被用来转换数据库的关系样式时,会带来一定的性能损失。

查询与结果

本文使用了NorthWind数据库作为模型,并创建了一个简单查询:

(NorthwindEntities ne =  NorthwindEntities()) 
{
(Order o  ne.Orders)
    {
i = o.OrderID;
    }
}

测试时,我们的每个查询对整个848行数据进行了10次遍历。结果很有意思,第1次运行时耗费了4241毫秒,而接下来的每次运行则平均耗费13毫秒左右的时间。最耗时的一部分内容是ObjectContext的创建,而在执行任意一个访问数据库的操作时,都会有一些耗时的操作发生。

每次操作的百分比值可以给我们一些启示:

  • 装载元数据(11%)
  • 初始化元数据(14%)
  • 打开连接(8%)
  • 生成视图(56%)
  • 装载程序集(2%)
  • 跟踪(1%)
  • 实例化(7%)
  • 其它(1%)

耗时百分比值最大的是视图生成,它达到了惊人的56%。既然视图生成是造成性能损耗的罪魁祸首,那么开发人员最好是使用命令行工具EDM生成器(EdmGen.exe),运行时需要加上视图生成命令参数(/mode:ViewGeneration),它的输出内容为一个代码文件(C#或者VB.NET),可以包含在项目中。视图的预生成可以将启动时间降低到2933毫秒,而对于循环遍历操作,整个时间可以降低28%。生成视图并随着应用程序一起发布是提高性能的妙方,但其缺点则在于视图不再是动态的,一旦模型发生改变,就需要重新生成以保持同步。

查询性能

需要指出的是关于性能的主要设计要素是查询缓存。一旦执行了查询,它的一部分内容就被维持在全局缓存中。由于查询与元数据缓存的存在,使得第二次运行的执行速度总是比第一次运行快。例如,如下的Entity SQL查询:

(PerformanceArticleContext ne =  PerformanceArticleContext()) 
{
ObjectQuery<Orders> orders = ne.CreateQuery<Orders>();
(Orders o orders)
{
i = o.OrderID;
}
}

第一次运行该查询耗时179毫秒,但下一次运行则只耗费了15毫秒的时间。首次运行与后续运行在执行方面的区别在于它构建了能够为执行传递provider的命令树(command tree)。

LINQ查询在执行方式上与Entity SQL查询相似。例如,下面的查询:

(PerformanceArticleContext ne =  PerformanceArticleContext()) 
{
var orders = from order ne.Orders
select order;
(Orders o orders)
{
i = o.OrderID;
}
}

首次执行LINQ查询耗时202毫秒,而随后的执行耗时18毫秒,两者的差距还要低于Entity SQL。可以看到,使用编译了的LINQ查询对于性能的提高更为明显。编译LINQ查询的好处在于它构建了表达树(expression tree),当查询被编译时,后续的执行就不需要重建表达树了。编译的LINQ查询代码看起来像这样:

Func<PerformanceArticleContext, IQueryable<Orders>> compiledQuery = CompiledQuery.Compile((PerformanceArticleContext ne) => (from o  ne.Orders select o)); 
(PerformanceArticleContext ne = PerformanceArticleContext())
{
(Orders o compiledQuery(ne))
{
i = o.OrderID;
}
}

注意,PerformanceArticleContext是一个委托。对于编译了的LINQ查询而言,第一次执行耗时305毫秒,而随后的执行时间则为15毫秒。结果并不惊人,值得关注的是编译的LINQ查询比之常规方式的LINQ查询,执行时间少了3毫秒。或许对于几个查询而言,这算不上什么,但如果有数以千计的查询,这样的性能提升就倍显价值所在了。

ADO.NET团队建议开发人员在查询中应谨慎使用Track/NoTrack选项:

在之前的例子中,所有放在对象创建中的查询结果都被添加到ObjectStateManager中,因此我们能够跟踪它们的更新。如果没有必要跟踪对象的更新和删除,那么最好是使用NoTracking合并项。例如,在一个ASP.NET Web应用程序中,如果它要查询一个指定的分类名称,但却不需要对返回的数据进行更新,那么NoTracking就会是一个不错的选择。在这种情形下,使用NoTracking的查询会在性能方面得到改善。

基于前面的一组数字,NoTracking选项能够大幅度地降低执行的时间,而其中性能的提升主要源自于我们停止了对变更的跟踪以及对关系的管理。如果使用NoTracking查询,无论是第一次执行还是随后的执行,编译的LINQ查询都要优于标准的LINQ查询。注意,编译的LINQ查询的第二次执行与Entity SQL查询的第二次执行相等。

ADO.NET团队同时还提醒开发者在创建查询时,有一些内容必须铭记于心:

在Entity框架中优化查询性能时,应该针对特定的编程场景做出最佳选择。这里列举了几个关键项:

  • ObjectContext的首次创建包含了装载和验证元数据的性能损耗。
  • 任何一个查询的首次执行都包含了构建一个查询缓存的性能损耗,以利于提高后续查询的执行速度。
  • 编译的LINQ查询比未编译的LINQ查询要快。
  • 如果不需要跟踪数据的变更与数据的关系,或者对大数据对象进行流操作,那么通过NoTracking合并项执行查询,效果会更佳。

若要了解更多关于ADO.NET和Entity框架的信息,敬请访问ADO.NET的团队博客

查看英文原文:Digging into the Performance of the ADO.NET Entity Framework

深度内容

和Google互补的搜索引擎Wolfram|Alpha

Wolfram|Alpha与Google究竟是什么关系,Wolfram|Alpha自己是如何定位的?Wolfram|Alaph在多大程度上是语义网搜索呢?InfoQ中文站就等等这些问题采访了Wolfram研究公司中国区商务经理王翔。

SOA契约成熟度模型

本文说明了所推荐的契约版本管理设计策略是如何与SOA成熟度模型发生联系的。文章目的是为实现版本管理和可组合性提供一个路线图。

数据服务简介

Vijay Narayanan在这篇文章中对数据服务的几个方面进行了介绍,它们都是SOA实践者和数据架构师感兴趣的内容。本文对数据服务的几个方面进行了介绍,包括需求定义,基本原理和好处、范围、开发以及消费模式。

分块云计算

在本文中,Jimmy Nilsson描述了一种他在过去数年间观察到的一种正在缓慢成长的架构风格,他把这种风格称为“分块云计算”。

豆瓣网技术架构变迁

罗马不是一天建成的,豆瓣的技术架构也是随着用户规模的增长一直在持续变化中。在本次演讲中,豆瓣的首席架构师洪强宁将与大家一起分享从上线时的单台服务器架构开始一直到现在的豆瓣架构变迁历程。

融合思想:深入探索S#arp架构

Billy McCafferty展示了S#arp架构,它在ASP.NET MVC框架的基础上,荟萃了当今的最佳实践,应用在ASP.NET Web应用程序的架构设计中。

王雷谈开源以及新兴市场计划

中国作为新兴市场中的新兴市场,是Sun在美国之外实施SSE(SUN Startup Essentials)项目重点关注的地区。在QCon Beijing 2009期间,InfoQ中文站有幸对此项目的负责人王雷先生进行了采访,探讨了关于开源、新兴市场、SSE等话题。

使用HTML5构建下一代的Web Form

HTML5 是由 WHATWG发起的,最开始的名称叫做Web Application 1.0,而后这个标准吸纳了Web Forms 2.0的标准,并一同被W3C组织所采用,合并成为下一代的HTML5标准。