使用EF4的ToList()上存在高锁争用和低性能



我有一个项目,它使用EF4(System.Data.Enties,没有nuget包),并直接命中LINQ查询的上下文。每当我通过让多个用户登录(使用VS测试负载测试)来进行负载测试时,我就会得到糟糕的性能,我的CPU会飙升到100%,并且VS.NET会在.NET锁争用率高和垃圾回收率高的情况下发出警报。

在进行大量的分析和调整时,一切似乎都指向LINQ查询本身的执行(在某种程度上是意料之中的),以及我们在结果的许多地方对.ToList()调用进行的大量争用。

有人经历过这种情况吗?原因是什么?我该如何解决?出于某种原因,我是否需要线程出.ToList()调用?

更新:一些人要求了解更多细节。。这是有问题的代码(经过一些调整以删除我无法发布的内容)。

var query =
from f in context.fs
where f.usr.Any((u) => u.id == id)
select new
{
FS = f,
f.fList,
E = from e in f.fList select new { e.er },
L = from l in f.fList select new { l.id }
};
var res = query.ToList();

在重负载下,相同的代码在多个线程上运行(profiler说13)。ToList()调用绝对是谋杀,它几乎解释了所有的延迟。

您需要记住,IQueryableIEnumerable方法使用不同的执行。特别是IQueryable方法,通常只不过是修改内存中的表达式树。从剖析者的角度来看,它们永远不会占用明显的时间。直到ToList调用,查询(由以前的IQueryable方法生成)才真正执行,这意味着实际的网络IO发生在那里。这样做所花费的时间将显著地高于所有查询生成的总和。

简言之,探查器在这里并没有真正帮助你。所有的工作都是通过ToList进行的,但决定查询效率的代码基本上都在其他地方。可能您想要的操作本身就很昂贵,或者您没有有效地编写查询(在这种情况下,我们需要了解更多信息才能提供信息)。

至于并行化是否会有所帮助,目前还不清楚。我的猜测可能不是。如果你的CPU已经用完了,那么你就让它保持忙碌,而线程只会增加更多的开销并降低速度。如果CPU没有任务,但速度很慢,那么线程更有可能对您有所帮助。

对于您的查询:如果您需要完整的fList实体在之后执行到EL的投影,则执行查询:

var query = from f in context.fs
where f.usr.Any((u) => u.id == id)
select new
{
FS = f,
f.fList
};
var res = query.ToList();
foreach (var x in res)
{
// runs in memory
var E = from e in x.fList select new e.er;
var L = from l in x.fList select new l.id;
// ...
}

如果你只需要E和L组合它们:

var query = from f in context.fs
where f.usr.Any((u) => u.id == id)
select new
{
FS = f,
EL = from x in f.fList select new { e.er, l.id }
};
var res = query.ToList();

最新更新