























EF Core 性能问题里,最折磨人的不是“慢”,而是“慢得没规律”,线上卡,测试又无法复现。
很多小D、小W同学都经历过这种现场:
你改了几个 Include,可能短期有效,但过两周又抖回来。根因往往不是某一行 LINQ 写错,而是整条排查链路没打通。
这篇文章就做一件事:给你一套能线上落地的 EF Core 慢查询定位闭环,从应用日志一路追到数据库执行计划,不靠猜。
真实场景:订单列表接口平时 80ms 左右,高峰时段 P95 抬到 1s。你的第一反应是“数据库是不是扛不住了”,但监控显示:
排查下来,又遇到两个组合问题:
也就是说,问题不是“不会优化 SQL”,而是“看不见慢点在哪”。
EF Core 的查询链路大致是:
很多排查第一步都把 SQL 抓出来看看,忽略了第 2、3 步的上下文信息,比如:
要拿到这些信息,最实用的组合就是:
TagWith:给 SQL 打业务标签OpenTelemetry:采集耗时、TraceId、SQL 标签并统一上报三者合起来,才能形成可服用、可验证的排查闭环。
public sealed record OrderListItemDto(
long Id,
string OrderNo,
string CustomerName,
decimal TotalAmount,
DateTime CreatedAtUtc);
public async Task<IReadOnlyList<OrderListItemDto>> QueryOrdersAsync(
AppDbContext db,
DateTime from,
DateTime to,
CancellationToken ct)
{
return await db.Orders
.TagWith("OrderListPage:v2")
.AsNoTracking()
.Where(x => x.CreatedAtUtc >= from && x.CreatedAtUtc < to)
.OrderByDescending(x => x.CreatedAtUtc)
.Take(100)
.Select(x => new OrderListItemDto(
x.Id,
x.OrderNo,
x.Customer.Name,
x.TotalAmount,
x.CreatedAtUtc))
.ToListAsync(ct);
}
TagWith 会把注释写进 SQL。你在数据库侧和日志侧都能直接看到 OrderListPage:v2,定位会快很多。
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using OpenTelemetry;
using OpenTelemetry.Trace;
public sealed class EfSqlTagEnricher : BaseProcessor<Activity>
{
private static readonly Regex EfTagLineRegex =
new(@"^\s*--\s*(?<tag>.+?)\s*$", RegexOptions.Compiled);
public override void OnEnd(Activity activity)
{
if (activity.Kind != ActivityKind.Client)
return;
// 只处理数据库调用 Span
var dbSystem = activity.GetTagItem("db.system")?.ToString();
if (string.IsNullOrWhiteSpace(dbSystem))
return;
var statement = activity.GetTagItem("db.statement")?.ToString();
if (string.IsNullOrWhiteSpace(statement))
return;
var tags = ExtractAllEfTags(statement);
if (tags.Count == 0)
return;
activity.SetTag("ef.tags", string.Join(" | ", tags));
activity.SetTag("ef.primary_tag", tags[0]);
}
private static IReadOnlyList<string> ExtractAllEfTags(string sql)
{
var tags = new List<string>();
using var reader = new StringReader(sql);
while (true)
{
var line = reader.ReadLine();
if (line is null)
break;
var trimmed = line.Trim();
if (trimmed.Length == 0)
continue;
var match = EfTagLineRegex.Match(line);
if (match.Success)
{
var tag = match.Groups["tag"].Value.Trim();
if (!string.IsNullOrWhiteSpace(tag))
tags.Add(tag);
continue;
}
// 遇到 SQL 正文后停止,避免把正文中的注释当成业务标签。
break;
}
return tags;
}
}
这段代码落地后,你就有了最关键的三类信息:
后面不管去日志平台还是数据库审计,排查效率都会提升一个量级。
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
const string serviceName = "efcore-sql-traces";
builder.Services.AddOpenTelemetry()
.ConfigureResource(r => r.AddService(serviceName))
.WithTracing(tracing =>
tracing
.AddAspNetCoreInstrumentation()
.AddEntityFrameworkCoreInstrumentation()
.AddSource("MySqlConnector")
.AddProcessor<EfSqlTagEnricher>()
.AddOtlpExporter(o =>
{
o.Endpoint = new Uri("http://localhost:4318/v1/traces");
o.Protocol = OtlpExportProtocol.HttpProtobuf;
})
);
当你在日志里找到慢 SQL 后,下一步是确认执行计划。分析问题本质,再相应的设计和实施解决方案。
EF Core 慢查询排查,不能每次都盯着 LINQ 本身看。从工程实践角度来讲,我建议你把把链路打通:
TagWith 把 SQL 和业务场景绑定起来OpenTelemetry 把耗时、TraceId、SQL 标签统一上报如果你现在的线上排查还是“看见慢了就加索引”,那就从这三步开始,先把可定位性建起来,再谈性能优化的上限。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。