.NET EFCode内存溢出事故一次记录

发布时间 2023-05-08 16:21:16作者: futurismme

 以下代码导致内存溢出:

是一段连表查询导致查询不到的问题

 var onelst = await dbpTasks.Where(Epr).Join(Context.DbpTaskMetadata.AsNoTracking().GroupBy(meta => meta.Taskid).Select(g => new { Key = g.Key, Value = g }), src => src.Taskid, meta => meta.Key, (src, meta) => new { src, meta }).Select(taskinfo => new TaskDbFullInfo
            {
                Dbptask = taskinfo.src,
                CaptureMeta = taskinfo.meta.Value.Where(x => x.Metadatatype == (int)MetaDataType.emCapatureMetaData).FirstOrDefault(),
                ContentMeta = taskinfo.meta.Value.Where(x => x.Metadatatype == (int)MetaDataType.emContentMetaData).FirstOrDefault(),
                MaterialMeta = taskinfo.meta.Value.Where(x => x.Metadatatype == (int)MetaDataType.emStoreMetaData).FirstOrDefault(),
                PlanningMeta = taskinfo.meta.Value.Where(x => x.Metadatatype == (int)MetaDataType.emPlanMetaData).FirstOrDefault(),
                SplitMeta = taskinfo.meta.Value.Where(x => x.Metadatatype == (int)MetaDataType.emSplitData).FirstOrDefault()
            }
           ).ToListAsync();

 

 DbpTaskMetadata全部查询分组了 ,耗时长内存也飙升,主要使用Group

sfenxiFenixql语句如下:

 EXPLAIN
 SELECT 
    dbptask.*,  
    capture_meta.metadata AS capture_metadata, 
    content_meta.metadata AS content_metadata, 
    material_meta.metadata AS material_metadata, 
    planning_meta.metadata AS planning_metadata, 
    split_meta.metadata AS split_metadata
FROM dbp_task dbptask
LEFT JOIN 
(
    SELECT taskid, metadata 
    FROM dbp_task_metadata 
    WHERE metadatatype = 1
) capture_meta 
ON dbptask.taskid = capture_meta.taskid
LEFT JOIN
(
    SELECT taskid, metadata 
    FROM dbp_task_metadata 
    WHERE metadatatype = 2
) content_meta
ON dbptask.taskid = content_meta.taskid 
LEFT JOIN  
(
    SELECT taskid, metadata 
    FROM dbp_task_metadata 
    WHERE metadatatype = 3
) material_meta
ON dbptask.taskid = material_meta.taskid
LEFT JOIN
(
    SELECT taskid, metadata 
    FROM dbp_task_metadata 
    WHERE metadatatype = 4
) planning_meta
ON dbptask.taskid = planning_meta.taskid
LEFT JOIN 
(
    SELECT taskid, metadata  
    FROM dbp_task_metadata 
    WHERE metadatatype = 5
) split_meta
ON dbptask.taskid = split_meta.taskid
 
WHERE dbptask.taskid<10;

 

 mysql分析:

 

可以看到时全表查询

本地调试运行现象内存飙升不止

 内存飙升分析

使用方式:

.Net Core在Linux环境dump分析_.net core dump_iamfrankjie的博客-CSDN博客

 如何修改:

如何不是后台服务添加请求接口日志,查找所有请求的接口记录.

定位那个请求连接 然后本地调试观察内存使用情况
然后结合内存情况分析

实现的原理梳理:

表1为任务表
表2为任务元数据表
表1的taskid对应表二taskid有多条记录
如何查询表1的一个taskid和类型对应记录为一个字段加表2对应的多条记录分别为一个字段

如果您需要在查询中添加任务元数据的类型条件,可以这样修改SQL语句:

sql
SELECT 
    t1.taskid AS taskid,
    (SELECT metadata FROM task_metadata WHERE taskid = t1.taskid AND type = 'type1' LIMIT 1) AS metadata1,
    (SELECT metadata FROM task_metadata WHERE taskid = t1.taskid AND type = 'type2' LIMIT 1 OFFSET 1) AS metadata2,
    (SELECT metadata FROM task_metadata WHERE taskid = t1.taskid AND type = 'type3' LIMIT 1 OFFSET 2) AS metadata3
FROM tasks t1

 msyql分析:

可以看到不存在全表查询

或者在LINQ查询中这样修改:

csharp
var result = from t in dbContext.Tasks  
            select new 
            {
                t.TaskId,
                Metadata1 = (from tm in dbContext.TaskMetadata  
                            where tm.TaskId == t.TaskId && tm.Type == "type1" 
                            select tm.Metadata).FirstOrDefault(),
                Metadata2 = (from tm in dbContext.TaskMetadata  
                            where tm.TaskId == t.TaskId && tm.Type == "type2"
                            select tm.Metadata).Skip(1).FirstOrDefault(),
                Metadata3 = (from tm in dbContext.TaskMetadata  
                            where tm.TaskId == t.TaskId && tm.Type == "type3"
                            select tm.Metadata).Skip(2).FirstOrDefault()                
            };

 只需要在查询TaskMetadata的条件中添加元数据类型的条件即可。如果某个类型的元数据不存在,相应的Metadata字段将为NULL。例如,如果task_metadata表还有一个Type列,数据如下:| taskid | metadata   | Type |
|:-:|:-:|:-:|
| 1 |  metadata1 | type1 |
| 1 |  metadata2 | type2 |
| 2 |  metadata3 | type2 |
| 2 |  metadata4 | type3 |那么查询结果为:| taskid | metadata1 | metadata2 | metadata3 |
|:-:|:-:|:-:|:-:|
| 1 | metadata1  | metadata2 | NULL |
| 2 | NULL | metadata3| metadata4 |   Metadata1字段包含type1元数据,Metadata2包含type2元数据,Metadata3包含type3元数据。请根据实际业务逻辑选择元数据的类型条件,我这里只作为一个示例。
优化

使用EF Core执行全表查询时,由于需要将整个表的数据加载到内存中,如果表过大 existed内存限制,很容易出现内存溢出的问题。有几种方法可以避免或减少此问题:1. 设置DbContext的MaxResultSetSize限制您可以在DbContext选项中设置MaxResultSetSize来限制返回行数,例如:

csharp
optionsBuilder.UseSqlServer(connectionString, o => o.MaxResultsSize(100)); 

 这将限制查询返回不超过100行记录,可以避免返回过多数据导致的内存溢出。2. 使用延迟加载和懒加载在EF Core中,默认启用延迟加载,这意味着关联数据只在真正访问时从数据库加载。您也可以通过设置属性的Load方法手动标记启用延迟加载,例如:

csharp
public class Blog 
{
    public int Id { get; set; }
    public string Title { get; set; }
    
    public virtual List<Post> Posts { get; set; }
}
 
modelBuilder.Entity<Blog>() 
    .HasMany(b => b.Posts)
    .WithOne(p => p.Blog)
    .HasForeignKey(p => p.BlogId)
    .IsRequired()
    .Load();  // 使用延迟加载

 这可以避免在查询博客时立即加载所有的文章数据,减少内存使用。3. 设置查询的PageSize并使用分页您可以在查询中设置PageSize来限制单次返回行数,然后使用分页逐步加载更多数据,而不是立即加载所有行。例如:

csharp 
int pageSize = 100;
var blogs = await dbContext.Blogs.Take(pageSize).ToListAsync();
var nextPage = await dbContext.Blogs.Skip(pageSize).Take(pageSize).ToListAsync();
// ...

这种分页查询方式可以充分控制每次加载的数据量,有效降低内存溢出的风险。4. 避免ToListAsync方法ToListAsync方法会将查询结果缓存到内存列表中,这会占用更多内存。您可以使用其他像Single、SingleOrDefault这样的查询方法,或者用foreach直接枚举查询的每个结果,而不调用ToListAsync。这些是使用EF Core时避免或减少全表查询导致内存溢出的几个常用方法。适当运用可以提高系统的性能和健壮性。