From:一条被猫抛弃的他乡流浪狗!

ado.net与EF的关系之EF生成SQL的效率研究

EF和SqlHelper 简单三层

  •  EF生成sql,再调用ado.net访问数据库,最后使结果对象具体化.
  •  之前的SqlHelper 简单三层的写法,拼接sql语句字符串,再调用ado.net访问数据库,最后也是把结果转换为对象.

明显的区别:sql语句的产生,EF是SQL查询命令和 LINQ 查询生成,SqlHelper简单三层是程序员直接拼接sql语句.
那么,一直谈EF性能,不得不说EF产生sql的速度.

回顾上篇文章提到的 EntityFramework性能之预生成视图 ,里面有张 查询执行的各个阶段 图解.
各个阶段如下:

  1. 加载元数据,中等成本,在每个应用程序域中一次.
  2. 打开数据库连接.(使用ado.net也免不了.)
  3. 生成视图,成本虽高,却在每个应用程序域中指执行一次.(上篇文章折腾了半天,最终觉得EF6.1.3和.net 4.5已经优化过了,不必在意这个预生成视图.)
  4. 准备查询,中等成本.每个唯一查询一次。注释:因为实体 SQL查询命令和 LINQ 查询现已缓存,所以,以后执行相同查询所需的时间较少。 您仍可以使用已编译的 LINQ 查询来降低后续执行中的这一开销,编译的查询比自动缓存的 LINQ 查询效率更高。
  5. 执行查询,低成本,每个查询一次。注释:使用 ADO.NET 数据提供程序对数据源执行命令的成本。 因为大多数数据源缓存查询计划,所以,以后执行相同查询所需的时间可能较少。
  6. 加载和验证类型,跟踪,使对象具体化

建立项目

(EF中还是用之前的PhoneBookModel.edmx,熟悉的名字.)
这次用asp.net mvc.在Home控制器下,有两个方法.

public ActionResult PreHot()
        {
            var db = new PhoneBookEntities();
            db.ContactInGroup.ToList();
            return View();
        }
PreHot
public ActionResult Test()
        {
            Stopwatch sw = new Stopwatch();
            sw.Start();

            using (var db = new PhoneBookEntities())
            {
                var gi = db.GroupInfo.FirstOrDefault(c => c.GroupName.Contains("g1!"));
                var ci = db.ContactInfo.FirstOrDefault(c => c.ID == 12);
                ci.ContactName += "!";
                gi.GroupName += "!";

                using (var tx = db.Database.BeginTransaction())
                {
                    try
                    {
                        db.Database.ExecuteSqlCommand("update GroupInfo set GroupName='hello' where GroupId=209");
                        db.SaveChanges();
                        tx.Commit();//此语句不要漏了,否则监控结果会是释放了事务,而不是提交了事务!
                    }
                    catch (Exception)
                    {
                        tx.Rollback();
                    }
                }
            }
            sw.Stop();
            ViewBag.time = sw.ElapsedMilliseconds;//在视图里显示花费的时间
            return View();
        }
Test

注意:之前文章提过,怎么监控sql语句.而在监控记录里,会给出执行sql语句的时间.对于Test方法中EF生成的sql语句,
记录显示:    执行-- 已在 1 毫秒内完成,结果为: SqlDataReader. 

测试开始:

  • a操作.清理解决方案,生成,先访问/home/index,再直接访问 /Home/Test,用时1250毫秒,再次访问 /Home/Test ,时间显示2-15毫秒.(/home/index,里面没任何代码,仅用来启动网站)
  • b操作.清理解决方案,生成,先访问/home/index,之后访问/Home/PreHot,再访问 /Home/Test,显示时间 250毫秒,再次访问 /Home/Test 时间显示2-15毫秒.

对于这个结果,我的解释是:
a操作,访问/Home/Test,执行Test()方法,而此时,要走 查询执行阶段的1,2,3,4,5,6,对比之前的 查询执行的各个阶段,就明白为什么会用时1250毫秒这么久(相比数据库执行查询只需要1毫秒.);

再次访问/Home/Test时, 查询执行阶段的1和3不用走(应用程序域中一次),4也不走(每个唯一查询一次[Test()方法里的查询执行过一次了,不唯一了,查询已自动缓存]),5执行查询(使用了缓存查询计划,使以后执行相同查询所需的时间可能较少),再走6.所以用时大幅度降低到2-15毫秒(显示时间大多在5毫秒左右,给个小公式:5毫秒=执行sql语句用时1毫秒+EF产生sql语句用时4毫秒).

b操作,访问/Home/PreHot,先让 查询执行阶段的1和3走了一次.再访问 /Home/Test,走4,5,6,显示时间 250毫秒(对比a操作首次访问 /Home/Test,少了对性能影响较大的1和3阶段).

再次访问 /Home/Test 时间显示2-15毫秒(同a操作).

也就是说,经过EF的初次使用,再加上 实体 SQL查询命令和 LINQ 查询 被自动缓存,满足这两个条件后,生成sql语句速度极快(应该不会大于5毫秒).
如果一个网站使用的是EF,稍微有点访问量,EF生成sql语句的速度已不是问题.(同样的测试Test()方法,在控制台里,时间一直是350毫秒左右...?) 

生成sql语句的速度已不是问题,那么接下来就是生成sql语句的质量.之前的博文,希望大家看过.
如果EF生成sql的质量够好,EF的效率就不是问题,我感觉其效率会无限接近原生的ado.net.
下篇,会拿 SqlHelper操作Ado.net跟使用EF做个对比.

如果,你初学EF,有一个表ContactInfo,含100W行数据, 而你写了一行这样的代码:
var res= db.ContactInfo.ToList().Skip(5).Take(5);
发现速度极慢,你就想EF真垃圾,我不使用EF,毫秒中搞定.再也不看EF啦.那就是你的损失了.自已用的不好,却在怪EF不行.

Ado.net和EF的关系,就好比c跟java和c#的关系.
理论上说,c的效率要比java和c#高.但java和c#还是被广泛使用.
但是,同样的功能,同样用c#去写,有的人写的程序会高效.因为他在用c#语言中,会避开耗性能的拆箱装箱,会更高效地去使用StringBuilder去拼接比较复杂的字符等.
c#语言本身的好的.但是程序员能不能高效地使用它又是另外一回事了.

同样的,EF也是好的,就看你对它的了解有多深入了.
会Ado.net,知道怎么写sql更优,明白怎么监控EF产生的sql语句,就会对瓶颈进行调优,就能把EF变成自己的利器.这也就是我先看EF产生的sql的原因.
如果什么都不知道,就用EF一顿瞎写,就好比用webForm一顿拖服务器控件, 把网址拖慢了,把人拖的脑残了(只会拖控件,换个jsp就懵逼了...估计控件拖的连请求,处理,响应都弄不明白.)
(博客园 dudu 关于EF的文章,我大多看了,不过版本是EF4.根据 dudu博文,博客园也是用了EF这个利器的.)

您认为EF如何呢? 

posted @ 2016-06-23 18:36  ICE_Inspire  阅读(2698)  评论(12编辑  收藏  举报