【问题标题】:Extremely slow EF startup - 15 minutesEF 启动极慢 - 15 分钟
【发布时间】:2018-01-25 11:21:54
【问题描述】:

前段时间我创建了一个系统,用户可以在其中为某些对象定义具有自定义字段的类别。然后,每个对象都有基于其类别的 FieldValue。以下类:

public class DbCategory
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }

        [Required]
        public TextDbField MainField { get; set; }
        public List<DbField> Fields { get; set; }
    }

 public class DbObject
    {
        public int Id { get; set; }
        public byte[] Bytes { get; set; }

        [Required]
        public DbCategory Category { get; set; }

        public TextDbFieldValue MainFieldValue { get; set; }
        public List<DbFieldValue> FieldsValues { get; set; }
    }

public abstract class DbField
    {
        public int Id { get; set; }

        [Required]
        public string Name { get; set; }

        [Required]
        public bool Required { get; set; }


    }


    public class IntegerDbField : DbField
    {
        public int? Minimum { get; set; }
        public int? Maximum { get; set; }
    }

    public class FloatDbField : DbField
    {
        public double? Minimum { get; set; }
        public double? Maximum { get; set; }

    }
//... few other types

  public abstract class DbFieldValue
    {
        [Key]
        public int Id { get; set; }
        [Required]
        public DbField Field { get; set; }

        [JsonIgnore]
        public abstract string Value { get; set; }
    }


    public class IntDbFieldValue : DbFieldValue
    {
        public int? IntValue { get; set; }

        public override string Value
        {
            get { return IntValue?.ToString(); }
            set
            {
                if (value == null) IntValue = null;
                else IntValue = int.Parse(value);
            }
        }
    }// and other FieldValue types

在我的开发机器(i5、16bg ram 和 ssd 驱动器)上,数据库(在 SqlExpress 中)有 4 个类别,每个类别有 5-6 个字段,10k 条记录,第一次查询大约需要 15 秒。第一个查询是

var result = db.Objects
     .Include(s => s.Category)
     .Include(s => s.Category.MainField)
     .Include(s => s.MainFieldValue.Field)
     .Include(s => s.FieldsValues.Select(f => f.Field))
     .Where(predicate ?? AlwaysTrue)
     .ToArray();

我这样做是为了将所有内容加载到内存中。然后,我处理缓存列表并将更改写入数据库。我这样做是因为用户可以在每个 FieldValue 上使用过滤器执行搜索。每次查询数据库都被证明很慢 - 但是这部分工作得很好。

问题稍后出现。有的客户定义了 6 个类别,每个类别有 20+ 个字段,存储 70k+ 条记录,启动有时需要 15 分钟以上。在那之后,5k和50k之间的速度没有区别。

我发现的每一种改进 EF Code First 启动时间的技术都主要考虑视图创建缓存、生成 EF 等,但在这种情况下,添加更多记录而不是更多实体类型后启动时间会增加。

我知道这是由于架构的复杂性造成的,但有什么方法可以加快速度吗?幸运的是,这是 Windows 服务,因此一旦启动,它会持续数周,但仍然如此。

我应该在第一次加载时删除 EF 并在纯 SQL 中执行吗?我应该分批这样做吗?我应该将 EF 更改为 nHibernate 吗?或者是其他东西?在执行此行期间,在虚拟化服务器上​​,该程序最大限度地利用了 CPU(不是 SQL 服务器,而是我的应用程序)。

我试过只加载对象,然后再加载它们的属性。这在小型数据库上要快一些(但不明显),但在大型数据库上更慢。任何帮助表示赞赏,即使答案是“忍住等待”。

【问题讨论】:

  • .ToArray(); 是您的问题。为什么不直接将其用作IQueryable
  • 因为我希望所有对象以后都可以在我们对 DB 的另一个查询中使用
  • 您想要所有个对象?有没有更好的方法来构建你的逻辑,这样它就不会那么耗费资源和缓慢?
  • 我想要所有对象,是的。如果稍后在工作站上一切都很快,我可以忍受缓慢的启动(因为它是一项服务)。但是16分钟是慢的。上面提到的带有过滤器的搜索必须检查每个 FieldValue,这意味着查询整个数据库(几乎,因为大多数搜索都没有过滤器)。目前,即使有 70k+ 条记录,搜索也是即时的。如果我每次都查询数据库,那么每次搜索都需要 15 分钟。它不是那么耗费资源,它在加载整个数据库的情况下远远低于 1GB 的 RAM
  • 我认为你最好分别加载所有实体并依靠关系修复来设置引用和集合。所有包含在多个级别中的查询必须是巨大的。

标签: c# sql-server entity-framework


【解决方案1】:

通过这些技巧,我设法将 EF 导致的总开始时间减少了 3 倍:

  1. 将框架更新到 6.2 并启用model caching

    公共类 CachingContextConfiguration : DbConfiguration { 公共缓存上下文配置() { SetModelStore(new DefaultDbModelStore(Directory.GetCurrentDirectory())); }

    }

  2. 尽早从新线程显式调用ctx.Database.Initialize()。这仍然需要 3-4 秒,但由于它与其他事情一起发生,因此很有帮助。

  3. 以合理的顺序将实体加载到 EF 缓存中。

之前,我只是在 Inlude 之后写了 Include,它翻译成多个连接。我在一些博客文章中发现了一个“经验法则”,最多两个链接的 Include EF 性能相当不错,但每多一个都会大大减慢一切。我还发现了一个blog post,它显示了 EF 缓存:一旦给定的实体使用 Include 或 Load 加载,它将自动放入适当的属性中(博客作者关于对象联合的错误)。所以我这样做了:

  using (var db = new MyContext())
            {
                db.Fields.Load();
                db.Categories.Include(c => c.MainField).Include(x => x.Fields).Load();
                db.FieldValues.Load();
                return db.Objects.Include(x => x.MainFieldValue.Field).ToArray();
            } 

这比问题中包含的数据快 6 倍。我认为一旦实体之前加载,EF引擎不会调用相关对象的数据库,它只是从缓存中获取它们。

  1. 我还在上下文构造函数中添加了这个:

        Configuration.LazyLoadingEnabled = false;
        Configuration.ProxyCreationEnabled = false;
    

其影响几乎不明显,但可能在庞大的数据集上发挥更大的作用。

我还观看了 this Rowan Miller 对 EF Core 的介绍,我将在下一个版本中切换到它 - 在某些情况下,它比 EF6 快 5-6 倍。

希望这对某人有所帮助

【讨论】:

  • 这些步骤将大型数据库的加载时间从 12 分钟缩短到 1 分钟以下。
  • 你能解释一下 ctx.Database.Initialize() 吗?如果我的 DbContext 与我的 Web 应用程序位于单独的程序集中,我将在哪里调用它?另外,这个方法需要一个布尔参数,我设置它是真还是假?
  • 例如,您可以在 Startup.cs 中执行此操作,并使用 false 调用它。您可以使用new MyContext().Database.Initialize(false) 或从容器中获取上下文。
猜你喜欢
  • 2023-03-25
  • 2018-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-16
  • 1970-01-01
  • 2021-02-26
相关资源
最近更新 更多