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语句:ide

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

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

            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查询,这下意识到了其中玄机了吧,下面咱们将上述再改造一下:spa

            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原理解析,敬请期待。

相关文章
相关标签/搜索