EF Core性能翻车?3秒变0.3秒的优化秘籍大公开!
大家好,我是.NET修仙日记的掌门人。今天要和大家分享的是EF Core查询性能优化的实战经验,这些都是我在实际项目中踩过的坑、趟过的雷,希望能帮助各位道友在数据访问层修炼出更高效的功法!
一、性能问题初现:一个慢查询引发的思考上周,我们收到用户反馈:"洞府列表加载太慢,要等3-4秒才能显示!" 使用SQL Server Profiler抓取到的查询:
代码语言:javascript复制SELECT [p].[Id], [p].[Name], [p].[OwnerId], [p].[CreateTime],
[o].[Id], [o].[Name], [o].[Level], [o].[VipLevel]
FROM [Palaces] AS [p]
LEFT JOIN [Immortals] AS [o] ON [p].[OwnerId] = [o].[Id]
WHERE [p].[Type] = 1
ORDER BY [p].[CreateTime] DESC
思考:这个看似简单的查询为何如此之慢?
二、性能诊断三板斧1. 使用EF Core的日志记录代码语言:javascript复制// Program.cs中配置EF Core日志
builder.Services.AddDbContext
options.UseSqlServer(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information));
2. 分析执行计划在SSMS中执行SET STATISTICS IO ON 后运行查询,发现:
逻辑读取次数高达1245次表扫描而非索引查找3. 使用Application Insights监控代码语言:javascript复制// 使用TelemetryClient记录查询耗时
var telemetry = new TelemetryClient();
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var palaces = await dbContext.Palaces
.Include(p => p.Owner)
.Where(p => p.Type == PalaceType.Normal)
.OrderByDescending(p => p.CreateTime)
.ToListAsync();
stopwatch.Stop();
telemetry.TrackMetric("PalaceQueryDuration", stopwatch.ElapsedMilliseconds);
三、六大优化神功🚀 神功一:选择性加载 - 只取所需优化前:
代码语言:javascript复制var palaces = await dbContext.Palaces
.Include(p => p.Owner) // 加载所有Owner信息
.ToListAsync();
优化后:
代码语言:javascript复制var palaces = await dbContext.Palaces
.Select(p => new PalaceDto
{
Id = p.Id,
Name = p.Name,
OwnerName = p.Owner.Name, // 只取需要的字段
CreateTime = p.CreateTime
})
.ToListAsync();
效果: 查询数据量减少60%,执行时间从1200ms降至400ms
⚡ 神功二:分页查询 - 化整为零优化前:
代码语言:javascript复制// 加载全部数据
var allPalaces = await dbContext.Palaces.ToListAsync();
优化后:
代码语言:javascript复制var pageSize = 20;
var pageNumber = 1;
var palaces = await dbContext.Palaces
.OrderBy(p => p.CreateTime)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
效果: 首次加载时间从3s降至200ms
🔍 神功三:索引优化 - 打通任督二脉在DbContext中配置索引:
代码语言:javascript复制modelBuilder.Entity
.HasIndex(p => new { p.Type, p.CreateTime })
.IncludeProperties(p => new { p.Name, p.OwnerId });
生成的查询将使用复合索引,性能提升显著。
🧠 神功四:AsNoTracking - 无状态查询优化前:
代码语言:javascript复制var palace = await dbContext.Palaces.FirstAsync(p => p.Id == id);
优化后:
代码语言:javascript复制var palace = await dbContext.Palaces
.AsNoTracking()
.FirstAsync(p => p.Id == id);
适用场景: 只读查询,不需要更新实体时使用
🧩 神功五:批量操作 - 一气呵成优化前:
代码语言:javascript复制foreach (var item in items)
{
dbContext.Add(item);
await dbContext.SaveChangesAsync(); // 每次保存
}
优化后:
代码语言:javascript复制dbContext.AddRange(items);
// 批量保存
await dbContext.SaveChangesAsync();
效果: 1000条数据插入从30s降至1s
🎯 神功六:编译查询 - 预编译功法优化前:
代码语言:javascript复制// 每次执行都会重新编译
var palace = await dbContext.Palaces
.FirstOrDefaultAsync(p => p.Id == id);
优化后:
代码语言:javascript复制private static readonly Func
EF.CompileAsyncQuery((CultivationDbContext db, int id) =>
db.Palaces.FirstOrDefault(p => p.Id == id));
// 使用预编译查询
var palace = await GetPalaceById(dbContext, id);
效果: 高频查询性能提升40%
四、高级心法:EF Core 8新特性1. 批量更新/删除代码语言:javascript复制// 传统方式
var palaces = await dbContext.Palaces
.Where(p => p.Level < 5)
.ToListAsync();
foreach (var p in palaces)
{
p.Level = 5;
}
await dbContext.SaveChangesAsync();
// EF Core 7+ 批量更新
await dbContext.Palaces
.Where(p => p.Level < 5)
.ExecuteUpdateAsync(setters => setters.SetProperty(p => p.Level, 5));
2. JSON列查询代码语言:javascript复制// 查询JSON列中的属性
var palaces = await dbContext.Palaces
.Where(p => p.MetadataJson.Contains("\"VIP\": true"))
.ToListAsync();
五、性能优化前后对比指标
优化前
优化后
提升幅度
洞府列表加载
3200ms
450ms
7倍
批量插入1000条
30s
1.2s
25倍
CPU占用峰值
85%
25%
大幅降低
内存消耗
420MB
180MB
57%减少
六、修仙感悟经过这一轮优化,我深刻体会到EF Core性能优化的几个关键点:
知彼知己: 先测量,再优化,不要凭猜测有所取舍: 不是所有场景都需要极致优化与时俱进: 善用EF Core的新特性综合调理: 数据库设计、索引、查询方式要协同优化(点击关注,修炼不迷路👇)
▌转载请注明出处,渡人渡己
🌟 感谢道友结缘! 若本文助您突破修为瓶颈,不妨[打赏灵丹]或[转发功德],让更多.NET道友共参CLR天道玄机。修真之路漫漫,我们以代码为符,共绘仙途!