实体数据查询和内存泄漏
本文关键字:泄漏 内存 数据查询 实体 | 更新日期: 2023-09-27 18:28:45
我在循环中下载了很多数据,但经过一些操作后,我删除了它们,但我看到内存分配增长非常快,只有几秒钟和1GB,所以如何在每次迭代后进行清理?
using (var contex = new DB)
{
var inputs = contex.AIMRInputs.Where(x => x.Input_Type == 1);
foreach (var input in inputs)
{
var data = contex.Values.Where(x => x.InputID == input.InputID).OrderBy(x => x.TimeStamp).ToList();
if (data.Count == 0) continue;
foreach (var value in data)
{
Console.WriteLine(Value.property);
}
data.Clear();
}
}
您可以做的第一件事是禁用更改跟踪,因为您没有更改代码中的任何数据。这可以防止加载的对象附加到上下文:
对于DbContext
(EF>=4.1):
var inputs = contex.AIMRInputs.AsNoTracking()
.Where(x => x.Input_Type == 1);
和:
var data = contex.Values.AsNoTracking()
.Where(x => x.InputID == input.InputID)
.OrderBy(x => x.TimeStamp)
.ToList();
编辑
对于EF 4.0,您可以保持查询原样,但在using
块中添加以下内容作为前两行:
contex.AIMRInputs.MergeOption = MergeOption.NoTracking;
contex.Values.MergeOption = MergeOption.NoTracking;
这将禁用ObjectContext
的更改跟踪。
编辑2
特别是参考@James Reategui在下面的评论,AsNoTracking
减少了内存占用:
这通常是真的(就像这个问题的模型/查询中一样),但并不总是这样!实际上,使用AsNoTracking
可能会对内存使用产生反作用。
当对象在内存中具体化时,AsNoTracking
会做什么?
第一:它不将实体附加到上下文,因此不会在上下文的状态管理器中创建条目。这些条目消耗内存。当使用POCO时,条目包含实体首次加载/附加到上下文时的属性值的快照-基本上是除了对象本身之外的所有(标量)属性的副本。因此,当不应用
AsNoTracking
时,所消耗的内存大约是对象大小的两倍。第二:另一方面,当实体没有连接到上下文时,EF无法利用键值和对象引用标识之间的标识映射的优势。这意味着具有相同键的对象将被物化多次,这会消耗额外的内存,而在不使用
AsNoTracking
的情况下,EF将确保实体每个键值只被物化一次。
当加载相关实体时,第二点变得尤为重要。简单示例:
比方说,我们有一个Order
和一个Customer
实体,一个订单有一个客户Order.Customer
。假设Order
对象大小为10字节,Customer
对象大小为20字节。现在我们运行这个查询:
var orderList = context.Orders
.Include(o => o.Customer).Take(3).ToList();
假设所有3个加载的订单都分配了相同的客户。因为我们没有禁用跟踪EF将实现:
- 3个订单对象=3x10=30字节
- 1个客户对象=1x20=20字节(因为上下文识别出所有3个订单的客户都是相同的,所以它只具体化了一个客户对象)
- 具有原始值的3个订单快照条目=3x10=30字节
- 1个具有原始值的客户快照条目=1x20=20字节
总和:100字节
(为了简单起见,我假设具有复制属性值的上下文条目与实体本身的大小相同。)
现在,我们在禁用更改跟踪的情况下运行查询:
var orderList = context.Orders.AsNoTracking()
.Include(o => o.Customer).Take(3).ToList();
物化数据为:
- 3个订单对象=3x10=30字节
- 3(!)个客户对象=3x20=60字节(无标识映射=每个键有多个对象,所有三个客户对象都具有相同的属性值,但它们在内存中仍然是三个对象)
- 没有快照条目
总和:90字节
因此,在这种情况下,使用AsNoTracking
查询消耗的内存减少了10字节
现在,同样的计算有5个订单(Take(5)
),同样所有订单都有相同的客户:
无AsNoTracking
:
- 5个订单对象=5x10=50字节
- 1个客户对象=1x20=20字节
- 具有原始值的5个订单快照条目=5x10=50字节
- 1个具有原始值的客户快照条目=1x20=20字节
总和:140字节
带AsNoTracking
:
- 5个订单对象=5x10=50字节
- 5(!)个客户对象=5x20=100字节
- 没有快照条目
总和:150字节
这次使用AsNoTracking
的成本要高出10个字节
上面的数字非常粗略,但有些地方是盈亏平衡点,使用AsNoTracking
可能需要更多内存。
使用或不使用AsNoTracking
之间的内存消耗差异很大程度上取决于查询、模型中的关系以及查询加载的具体数据。例如:当上述示例中的订单全部(或大部分)都有不同的客户时,AsNoTracking
在内存消耗方面总是更好。
结论:AsNoTracking
主要是用来提高查询性能的工具,而不是内存使用率。在许多情况下,它也会消耗更少的内存。但是,如果一个特定的查询使用AsNoTracking
需要更多的内存,也不要感到惊讶。最后,您必须测量内存占用,才能做出支持或反对AsNoTracking
的可靠决策。
如果这里的问题可能与DataContext有关,请分开。它们中的许多在执行查询时缓存信息或存储附加信息,因此其内存占用将随着时间的推移而增加。我会先与探查器核实一下,但如果这是你的问题,你可能需要在每次X请求后重新创建一个新的数据上下文(用不同的X值进行实验,看看什么最有效)。
我还想指出,大多数人往往记忆力很强。在开始进行这些类型的优化之前,您应该真正确定您使用的内存超过了真正可接受的内存。GC还将开始更积极地清除内存,因为您可以使用的可用内存更少。它不会过早地优化(您也不应该)。