EntityFramework Core不得不注意的性能优化意外收获,你会用错?

前言

这两天在着实研究EF Core项目当中对于一些查询也没实际去检测,于是想着利用放假时间去实际测试下,结果本文就出来了,too young,too simple,后续博主会从底层翻译表达式树弄起,来从源头了解EF Core,通过本文你会明白不是EF Core团队没做性能优化,而是你根本就没用过而且正在倒退。

EntityFramework Core性能优化初探

简单粗暴直接上代码,给出上下文以及需要用到的测试类,如下:

    public class EFCoreContext : DbContext
    {
        public DbSet<Blog> Blogs { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            => optionsBuilder.UseSqlServer(@"Server=.;Database=EFCoreDb;Trusted_Connection=True;");

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Blog>(pc =>
            {
                pc.ToTable("Blog").HasKey(k => k.Id);

                pc.Property(p => p.Name).IsRequired();
                pc.Property(p => p.Url).IsRequired();
                pc.Property(p => p.Count).IsRequired();

                pc.Property(p => p.RowVersion).IsRequired().IsRowVersion().ValueGeneratedOnAddOrUpdate();
            });
        }
    }

你是否像如下去获取分页数据呢,我们来一起瞧瞧:

            var ef = new EFCoreContext();
            var blogs = ef.Blogs;

            var example1 = blogs
                .Skip(1)
                .Take(1)
                .ToList();

            var example2 = blogs
                .Skip(10)
                .Take(10)
                .ToList();

我们通过如下SQL语句来查看查询计划生成的SQL语句:

SELECT
    sys.syscacheobjects.cacheobjtype,
    sys.dm_exec_query_stats.execution_count,
    sys.syscacheobjects.SQL,
    sys.dm_exec_query_plan.query_plan

FROM sys.dm_exec_query_stats
    INNER JOIN sys.dm_exec_cached_plans
     ON sys.dm_exec_cached_plans.plan_handle = sys.dm_exec_query_stats.plan_handle
    INNER JOIN sys.syscacheobjects ON sys.syscacheobjects.bucketid = sys.dm_exec_cached_plans.bucketid

CROSS APPLY sys.dm_exec_query_plan(sys.dm_exec_query_stats.plan_handle)

结果如下:

我们再来看看xml文件中生成的SQL语句是怎样的。

这说明什么问题呢,上述查询计划中生成的SQL语句对于我们上述去取数据首选从第二条取一条,接下来是去取第十条后的十条,同时上述SQL语句而是声明了两个变量,换言之,上述两条语句查询最终在第一次查询后SQL查询计划进行了缓存,下次再去取数据时直接调用此SQL语句以此达到重用的目的,下面要是我们进行如下改造,结果会怎样呢?

            var ef = new EFCoreContext();
            var blogs = ef.Blogs;

            var count = 1;
            var example1 = blogs
                .Skip(count)
                .Take(count)
                .ToList();

            count = 10;
            var example2 = blogs
                .Skip(count)
                .Take(count)
                .ToList();

结果经过上述改造利用变量的形式和直接赋值的形式是一致的,没有什么可讲的,下面我们再来讲述另外一种情况。请继续往下看。

            var ef = new EFCoreContext();
            var blogs = ef.Blogs;

            var skipTakeWithInt1 = blogs
                .OrderBy(b => b.Id).Where(d => d.Name.Length > 1).ToList();

            var skipTakeWithInt2 = blogs
                .OrderBy(b => b.Id).Where(d => d.Name.Length > 10).ToList();

看出什么没有,对于上述两条查询则是对应进行了两次SQL查询,这下意识到了其中玄机了吧,下面我们将上述再改造一下:

            var ef = new EFCoreContext();
            var blogs = ef.Blogs;

            var length = 1;
            var skipTakeWithVariable1 = blogs
                .OrderBy(b => b.Id).Where(d => d.Name.Length > length).ToList();

            length = 10;
            var skipTakeWithVariable2 = blogs
                .OrderBy(b => b.Id).Where(d => d.Name.Length > length).ToList();

我们利用变量替换值改造后结果生成的SQL语句与上述截然不同,这里同样是声明了长度的变量,你发现没该变量居然和我们声明的变量长度是一致的,有点神奇,如此这样通过变量替换值得方式来达到SQL查询计划重用的目的,说到这里,我们是不是应该回答一下为什么会有这样的情况发生呢。很多东西当你一直没用到时就觉得不会用到压根不用学,其实不然,比如这样,其本质到底是怎样的呢,其实是因为前面我已经讲过【闭包】的原因。

 

lambda表达式对我们声明的变量进行了捕获然后延长了其生命周期,也就是说将变量类似变成类中一个字段了,类似如下:

    [CompilerGenerated]
    public sealed class ExampleClass1
    {
        public int Length;
    }

    [CompilerGenerated]
    public sealed class ExampleClass2
    {
        public int Length;
    }

自动编译生成两个类同时存在两个对于长度的字段。接下来当我们利用变量进行查询就演变了如下这样:

            var ef = new EFCoreContext();
            var blogs = ef.Blogs;

            var length = 1;
            var example1 = new ExampleClass1() { Length = length };
            var skipTakeWithVariable1 = blogs
                .OrderBy(b => b.Id).Where(d => d.Name.Length > example1.Length).ToList();

            length = 10;
            var example2 = new ExampleClass2() { Length = length };
            var skipTakeWithVariable2 = blogs
                .OrderBy(b => b.Id).Where(d => d.Name.Length > example2.Length).ToList();

这样就很明了了为什么通过变量达到查询计划重用的目的。

总结

关于EntityFramework Core虽然目前设计的性能非常好,但是有些东西等我们去用时还得多加验证,看其背后的本质是怎样的,才能不会心生疑窦,到底该怎样用,如何用,心中要有定数才是,一点一滴的细小优化不注意最终将导致大意失荆州。接下来博主会在三个方向不定时更新博客,第一个是SQL Server性能优化系列,第二个是ASP.NET Core MVC/WebAPi,第三个则是EntityFramework Core原理解析,敬请期待。

时间: 2024-08-16 15:01:48

EntityFramework Core不得不注意的性能优化意外收获,你会用错?的相关文章

EntityFramework之原始查询及性能优化(六)

前言 在EF中我们可以通过Linq来操作实体类,但是有些时候我们必须通过原始sql语句或者存储过程来进行查询数据库,所以我们可以通过EF Code First来实现,但是SQL语句和存储过程无法进行映射,于是我们只能手动通过上下文中的SqlQuery和ExecuteSqlCommand来完成. SqlQuery sql语句查询实体  通过DbSet中的SqlQuery方法来写原始sql语句返回实体实例,如果是通过Linq查询返回的那么返回的对象将被上下文(context)所跟踪. 首先给出要操作

EntityFramework之异步、事务及性能优化(九)

前言 本文开始前我将循序渐进先了解下实现EF中的异步,并将重点主要是放在EF中的事务以及性能优化上,希望通过此文能够帮助到你. 异步 既然是异步我们就得知道我们知道在什么情况下需要使用异步编程,当等待一个比较耗时的操作时,可以用异步来释放当前的托管线程而无需等待,从而在管理线程中不需要花费额外的时间,也就是不会阻塞当前线程的运行. 在客户端如:Windows Form以及WPF应用程序中,当执行异步操作时,则当前线程能够保持用户界面持续响应.在服务器端如:ASP.NET应用程序中,执行异步操作可

前端性能优化(三)——传统 JavaScript 优化的误区

注:本文是纯技术探讨文,无图无笑点,希望您喜欢 一.前言 软件行业极其缺乏前端人才这是圈内的共识了,某种程度上讲,同等水平前端的工资都要比后端高上不少,而圈内的另一项共识则是--网页是公司的脸面! 几年前,谷歌的一项统计表明,如果亚马逊页面加载每慢 100ms,将影响他们 1% 的收入:如果谷歌页面加载慢 500ms,流量将锐减 20%,这个数据现在必将更加恐怖! 在前端高性能优化(一).(二)中,笔者介绍了一些关于前端优化的技术,这些技术都依赖于前人的辛苦努力,但我们仍要明白的是,前端的情况十

tomcat服务器的性能优化

http://blog.csdn.net/Core__Code/article/details/9669269 Tomcat 生产服务器性能优化 简介 考虑一下这种场景,你开发了一个应用,它有十分优秀的布局设计,最新的特性以及其它的优秀特点.但是在性能这方面欠缺,不管这个应用如何都会遭到客户拒绝.客户总是期望它们的应用应该有更好的性能.如果你在产品中使用了Tomcat服务器,那么这篇文章就会给你几方面来提升Tomcat服务器的性能.感谢ITWorld article给本文提供资源.经过沉思我已经

《高性能Linux服务器构建实战》——1.6节Nginx性能优化技巧

1.6 Nginx性能优化技巧 1.6.1 编译安装过程优化1.减小Nginx编译后的文件大小在编译Nginx时,默认以debug模式进行,而在debug模式下会插入很多跟踪和ASSERT之类的信息,编译完成后,一个Nginx要有好几兆字节.而在编译前取消Nginx的debug模式,编译完成后Nginx只有几百千字节.因此可以在编译之前,修改相关源码,取消debug模式.具体方法如下:在Nginx源码文件被解压后,找到源码目录下的auto/cc/gcc文件,在其中找到如下几行: # debug

EntityFramework Core并发深挖详解,一纸长文,你准备好看完了吗?

前言 之前有关EF并发探讨过几次,但是呢,博主感觉还是有问题,为什么会觉得有问题,其实就是理解不够透彻罢了,于是在项目中都是用的存储过程或者SQL语句来实现,利用放假时间好好补补EF Core并发的问题,本文比较长,请耐心点看. EntityFramework Core并发初级版初探 关于并发无非就两种:乐观并发和悲观并发,悲观并发简言之则是当客户端对数据库中同一值进行修改时会造成阻塞,而乐观并发则任何客户端都可以对可以对数据进行查询或者读取,在EF Core中不支持悲观并发,结果则产生并发冲突

EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解

前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6.x中不同,相同的则不再叙述. EntityFramework Core 1.1方法理论详解 当我们利用EF Core查询数据库时如果我们不显式关闭变更追踪的话,此时实体是被追踪的,关于变更追踪我们下节再叙.就像我们之前在EF 6.x中讨论的那样,不建议手动关闭变更追踪,对于有些特殊情况下,关闭变更追

EntityFramework Core饥饿加载忽略导航属性问题

前言 .NET Core项目利用EntityFramework Core作为数据访问层一直在进行中,一直没有过多的去关注背后生成的SQL语句,然后老大捞出日志文件一看,恩,有问题了,所以本文产生了,也是有点疑惑,若有知情者,还望告知. EntityFramework Core忽略导航属性  在前面我们已经探讨过利用Serilog日志框架来输出日志,所以对于本节查询日志的输出依然借助Seilog.我们在Startup.cs类中Starup方法中是创建日志实例. Log.Logger = new L

ASP.NET性能优化小结(ASP.NET&amp;amp;C#)

ASP.NET: 一.返回多个数据集 检查你的访问数据库的代码,看是否存在着要返回多次的请求.每次往返降低了你的应用程序的每秒能够响应请求的次数.通过在单个数据库请求中返回多个结果集,可以减少与数据库通信的时间,使你的系统具有扩展性,也可以减少数据库服务器响应请求的工作量. 如果用动态的SQL语句来返回多个数据集,那用存储过程来替代动态的SQL语句会更好些.是否把业务逻辑写到存储过程中,这个有点争议.但是我认为,把业务逻辑写到存储过程里面可以限制返回结果集的大小,减小网络数据的流量,在逻辑层也不