EF Core 延迟加载极慢
EF Core Lazy loading extremely slow
我做了一个启用懒惰的小测试loading.optionsBuilder.UseLazyLoadingProxies().UseSqlServer(ConnectionString);
(使用 EF Core 2.1.4)
我循环使用和不使用仪器,这是我得到的结果
案例一
var instruments = db.instruments.OrderBy(t=>t.id).Include(t=>t.NavPro1).ThenInclude(t=>t.NavPro2).Take(200);
案例 2
var instruments = db.instruments.OrderBy(t=>t.id).Include(t=>t.NavPro1).ThenInclude(t=>t.NavPro2).Take(200);
然后
foreach (var i in instruments)
{
var props = i.NavPro1;
foreach (var prop in props)
{
sbPrintGreeks.AppendLine(prop.NavPro2.Name + " - " + prop.id + " - " + prop.Value);
}
}
在没有延迟加载的情况下需要 7 秒来获取 100k 行
延迟加载需要 160 秒才能获得 3k 行。
如何才能获得不错的性能?
这是一个普遍的问题,称为 N+1 问题。
这里发生的事情是当你使用延迟加载时你有更多的请求。
在您使用 Include
的情况下,您有一个巨大的请求可以为您提供所有数据 - 或者每个 table 可能有一个请求,所以在您的情况下有三个请求。这取决于您的数据的确切结构。
在延迟加载的情况下,您对仪器有一个请求,并且对于每个仪器,您都有另一个 NavPro1 请求。对于每个 NavPro1
元素,您还有另一个 NavPro2 请求。
因此,如果您有 1000 台仪器,并且每台仪器有 10 个 NavPro1,那么您现在有 1 + (1000 * (1 + 10)) = 11001 个请求,而不是最多三个请求。太慢了,期间。
是的,避免延迟加载。期间.
问题是 - 总是,回到曾经构建的每个 ORM - 如果你进行延迟加载,每个引用都是延迟加载。这是 1+ 次往返(每 属性,最少一次)。分开 SQL 执行,分开网络时间。这加起来非常快。
这就是为什么每个 ORM 都支持非延迟加载,在 EF 变体中通过 .Include 语句扩展 SQL,或生成单独的 SQL(ef core,tolist与高效 sql).
建立关系
如果您坚持使用延迟加载 - 正如您的问题所暗示的那样,您不会在代码上撒任何魔法粉来避免延迟加载的隐含负面影响。
现在,除此之外 - 3.0 之前的任何 EF Core 都已损坏。是的,那是 3.0——甚至不是 2.2。看,各个部分有很多问题,包括非常基本的 LINQ。和性能。至少 2.2(希望在一个月内发布)应该可以解决一些问题。在那之前,请尝试使用 .Include 和 AsNoTracking - 因为 IIRC 存在一个性能错误,在加载 20 万行时也可能会影响您。
https://github.com/aspnet/EntityFrameworkCore/issues/12451
这是解决方案
服务
.AddDbContext(b => b.ReplaceService());
我做了一个启用懒惰的小测试loading.optionsBuilder.UseLazyLoadingProxies().UseSqlServer(ConnectionString);
(使用 EF Core 2.1.4)
我循环使用和不使用仪器,这是我得到的结果
案例一
var instruments = db.instruments.OrderBy(t=>t.id).Include(t=>t.NavPro1).ThenInclude(t=>t.NavPro2).Take(200);
案例 2
var instruments = db.instruments.OrderBy(t=>t.id).Include(t=>t.NavPro1).ThenInclude(t=>t.NavPro2).Take(200);
然后
foreach (var i in instruments)
{
var props = i.NavPro1;
foreach (var prop in props)
{
sbPrintGreeks.AppendLine(prop.NavPro2.Name + " - " + prop.id + " - " + prop.Value);
}
}
在没有延迟加载的情况下需要 7 秒来获取 100k 行
延迟加载需要 160 秒才能获得 3k 行。
如何才能获得不错的性能?
这是一个普遍的问题,称为 N+1 问题。
这里发生的事情是当你使用延迟加载时你有更多的请求。
在您使用 Include
的情况下,您有一个巨大的请求可以为您提供所有数据 - 或者每个 table 可能有一个请求,所以在您的情况下有三个请求。这取决于您的数据的确切结构。
在延迟加载的情况下,您对仪器有一个请求,并且对于每个仪器,您都有另一个 NavPro1 请求。对于每个 NavPro1
元素,您还有另一个 NavPro2 请求。
因此,如果您有 1000 台仪器,并且每台仪器有 10 个 NavPro1,那么您现在有 1 + (1000 * (1 + 10)) = 11001 个请求,而不是最多三个请求。太慢了,期间。
是的,避免延迟加载。期间.
问题是 - 总是,回到曾经构建的每个 ORM - 如果你进行延迟加载,每个引用都是延迟加载。这是 1+ 次往返(每 属性,最少一次)。分开 SQL 执行,分开网络时间。这加起来非常快。
这就是为什么每个 ORM 都支持非延迟加载,在 EF 变体中通过 .Include 语句扩展 SQL,或生成单独的 SQL(ef core,tolist与高效 sql).
建立关系如果您坚持使用延迟加载 - 正如您的问题所暗示的那样,您不会在代码上撒任何魔法粉来避免延迟加载的隐含负面影响。
现在,除此之外 - 3.0 之前的任何 EF Core 都已损坏。是的,那是 3.0——甚至不是 2.2。看,各个部分有很多问题,包括非常基本的 LINQ。和性能。至少 2.2(希望在一个月内发布)应该可以解决一些问题。在那之前,请尝试使用 .Include 和 AsNoTracking - 因为 IIRC 存在一个性能错误,在加载 20 万行时也可能会影响您。
https://github.com/aspnet/EntityFrameworkCore/issues/12451
这是解决方案
服务 .AddDbContext(b => b.ReplaceService());