EF Core 与 MySQL:日志和调试详解

本文将详细讲解EF Core与MySQL的日志和调试,分为三个部分:EF Core日志配置、MySQL日志查看以及使用调试工具分析查询性能。

1. 配置 EF Core 日志

基本日志配置

// 在DbContext配置中启用日志记录
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    var connectionString = "server=localhost;database=efcoredb;user=root;password=yourpassword";
    
    optionsBuilder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
        .EnableSensitiveDataLogging() // 记录参数值(仅开发环境)
        .EnableDetailedErrors() // 提供更详细的错误信息(仅开发环境)
        .LogTo(Console.WriteLine, LogLevel.Information) // 输出到控制台
        .LogTo(
            message => Debug.WriteLine(message), // 输出到调试输出
            LogLevel.Debug
        );
}

使用 ILoggerFactory(ASP.NET Core 应用)

// 在Program.cs或Startup.cs中配置
public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<ApplicationDbContext>(options =>
    {
        var connectionString = Configuration.GetConnectionString("DefaultConnection");
        options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
            .EnableSensitiveDataLogging(Environment.IsDevelopment())
            .EnableDetailedErrors(Environment.IsDevelopment())
            .UseLoggerFactory(LoggerFactory.Create(builder =>
            {
                builder
                    .AddConsole()
                    .AddDebug()
                    .AddFilter(DbLoggerCategory.Database.Command.Name, LogLevel.Information);
            }));
    });
}

自定义日志过滤器

// 只记录执行时间超过100ms的查询
optionsBuilder.LogTo(
    message =>
    {
        if (message.Contains("CommandExecuted") && message.Contains("Elapsed"))
        {
            var elapsedMatch = Regex.Match(message, @"Elapsed:(\d+)ms");
            if (elapsedMatch.Success && int.Parse(elapsedMatch.Groups[1].Value) > 100)
            {
                Console.WriteLine($"慢查询: {message}");
            }
        }
    },
    LogLevel.Information
);

结构化日志记录

// 使用Serilog等结构化日志框架
Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Information()
    .WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj}{NewLine}{Exception}")
    .WriteTo.File("logs/efcore-.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();

services.AddDbContext<ApplicationDbContext>(options =>
{
    options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
        .LogTo(Log.Logger.Information, LogLevel.Information)
        .LogTo(Log.Logger.Warning, LogLevel.Warning)
        .LogTo(Log.Logger.Error, LogLevel.Error);
});

2. 查看 MySQL 日志

启用和配置 MySQL 日志

-- 查看当前日志配置
SHOW VARIABLES LIKE '%log%';

-- 启用通用查询日志(记录所有查询)
SET GLOBAL general_log = 'ON';
SET GLOBAL general_log_file = '/var/log/mysql/general.log';

-- 启用慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
SET GLOBAL long_query_time = 1; -- 超过1秒的查询视为慢查询

-- 启用二进制日志(用于复制和恢复)
SET GLOBAL log_bin = 'ON';

-- 启用错误日志(通常默认开启)
SHOW VARIABLES LIKE 'log_error';

在 MySQL 配置文件中设置日志

# /etc/mysql/my.cnf 或 /etc/mysql/mysql.conf.d/mysqld.cnf
[mysqld]
# 通用查询日志
general_log = 1
general_log_file = /var/log/mysql/general.log

# 慢查询日志
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1

# 错误日志
log_error = /var/log/mysql/error.log

# 二进制日志
log_bin = /var/log/mysql/mysql-bin.log
expire_logs_days = 10
max_binlog_size = 100M

分析 MySQL 日志

# 查看慢查询日志
sudo tail -f /var/log/mysql/slow.log

# 使用mysqldumpslow分析慢查询日志
sudo mysqldumpslow /var/log/mysql/slow.log

# 按执行时间排序查看最慢的查询
sudo mysqldumpslow -s t /var/log/mysql/slow.log

# 查看通用查询日志
sudo tail -f /var/log/mysql/general.log

# 使用pt-query-digest分析查询(需要安装Percona Toolkit)
sudo pt-query-digest /var/log/mysql/slow.log

在应用程序中记录 MySQL 性能指标

// 创建性能监控服务
public class MySqlPerformanceMonitor
{
    private readonly ApplicationDbContext _context;
    private readonly ILogger<MySqlPerformanceMonitor> _logger;

    public MySqlPerformanceMonitor(ApplicationDbContext context, ILogger<MySqlPerformanceMonitor> logger)
    {
        _context = context;
        _logger = logger;
    }

    public async Task MonitorQueryPerformanceAsync(Func<Task> databaseOperation, string operationName)
    {
        var stopwatch = Stopwatch.StartNew();
        
        try
        {
            await databaseOperation();
            stopwatch.Stop();
            
            if (stopwatch.ElapsedMilliseconds > 1000) // 超过1秒的记录为警告
            {
                _logger.LogWarning("慢查询检测: {Operation} 耗时 {ElapsedMs}ms", 
                    operationName, stopwatch.ElapsedMilliseconds);
            }
            else if (stopwatch.ElapsedMilliseconds > 500) // 超过0.5秒的记录为信息
            {
                _logger.LogInformation("查询性能: {Operation} 耗时 {ElapsedMs}ms", 
                    operationName, stopwatch.ElapsedMilliseconds);
            }
        }
        catch (Exception ex)
        {
            stopwatch.Stop();
            _logger.LogError(ex, "查询执行失败: {Operation} 耗时 {ElapsedMs}ms", 
                operationName, stopwatch.ElapsedMilliseconds);
            throw;
        }
    }
}

3. 使用调试工具分析查询性能

使用 EF Core 的诊断工具

// 启用诊断监听器
DiagnosticListener.AllListeners.Subscribe(new EfCoreDiagnosticListener());

public class EfCoreDiagnosticListener : IObserver<DiagnosticListener>
{
    public void OnNext(DiagnosticListener value)
    {
        if (value.Name == DbLoggerCategory.Name)
        {
            value.Subscribe(new EfCoreObserver());
        }
    }

    public void OnCompleted() { }
    public void OnError(Exception error) { }
}

public class EfCoreObserver : IObserver<KeyValuePair<string, object>>
{
    public void OnNext(KeyValuePair<string, object> value)
    {
        if (value.Key == RelationalEventId.CommandExecuted.Name)
        {
            var command = (IDbCommand)value.Value.GetType().GetProperty("Command").GetValue(value.Value);
            var duration = (TimeSpan)value.Value.GetType().GetProperty("Duration").GetValue(value.Value);
            
            if (duration.TotalMilliseconds > 100)
            {
                Debug.WriteLine($"慢查询: {command.CommandText} 耗时: {duration.TotalMilliseconds}ms");
            }
        }
    }

    public void OnCompleted() { }
    public void OnError(Exception error) { }
}

使用 Application Insights 监控

// 在Startup.cs中配置Application Insights
public void ConfigureServices(IServiceCollection services)
{
    services.AddApplicationInsightsTelemetry(Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"]);
    
    services.AddDbContext<ApplicationDbContext>(options =>
    {
        options.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString))
            .EnableSensitiveDataLogging()
            .AddApplicationInsightsDiagnostics(); // 如果支持的话
    });
}

// 或者手动记录依赖跟踪
public async Task<List<Product>> GetProductsAsync()
{
    var operation = telemetryClient.StartOperation<DependencyTelemetry>("MySQL Query");
    operation.Telemetry.Type = "MySQL";
    operation.Telemetry.Data = "SELECT * FROM Products";
    
    try
    {
        var products = await _context.Products.ToListAsync();
        operation.Telemetry.Success = true;
        return products;
    }
    catch (Exception ex)
    {
        operation.Telemetry.Success = false;
        telemetryClient.TrackException(ex);
        throw;
    }
    finally
    {
        telemetryClient.StopOperation(operation);
    }
}

使用 MiniProfiler

// 安装MiniProfiler.EntityFrameworkCore
// 在Startup.cs中配置
public void ConfigureServices(IServiceCollection services)
{
    services.AddMiniProfiler(options =>
    {
        options.RouteBasePath = "/profiler";
        options.ColorScheme = StackExchange.Profiling.ColorScheme.Auto;
        options.EnableMvcFilterProfiling = true;
        options.EnableMvcViewProfiling = true;
        options.TrackConnectionOpenClose = true;
    }).AddEntityFramework();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMiniProfiler();
    // ... 其他中间件
}

// 在视图中添加MiniProfiler
@inject StackExchange.Profiling.MiniProfiler Profiler
@MiniProfiler.RenderIncludes(Profiler)

使用 JetBrains dotTrace 或 dotMemory

// 在代码中添加性能分析点
public async Task<List<Product>> GetExpensiveProductsAsync()
{
    // 使用JetBrains Profiler API(需要安装JetBrains.Profiler.Api包)
    JetBrains.Profiler.Api.MeasureProfiler.StartCollectingData();
    
    try
    {
        var products = await _context.Products
            .Where(p => p.Price > 100)
            .Include(p => p.Category)
            .ToListAsync();
            
        return products;
    }
    finally
    {
        JetBrains.Profiler.Api.MeasureProfiler.SaveData();
    }
}

使用 MySQL EXPLAIN 分析查询计划

// 在代码中执行EXPLAIN分析查询
public async Task<string> ExplainQueryAsync(IQueryable query)
{
    var sql = query.ToQueryString();
    var explainSql = $"EXPLAIN {sql}";
    
    using (var command = _context.Database.GetDbConnection().CreateCommand())
    {
        command.CommandText = explainSql;
        _context.Database.OpenConnection();
        
        using (var result = await command.ExecuteReaderAsync())
        {
            var explanation = new StringBuilder();
            while (await result.ReadAsync())
            {
                for (int i = 0; i < result.FieldCount; i++)
                {
                    explanation.AppendLine($"{result.GetName(i)}: {result.GetValue(i)}");
                }
                explanation.AppendLine("---");
            }
            return explanation.ToString();
        }
    }
}

// 使用方式
var query = _context.Products.Where(p => p.Price > 100);
var explanation = await ExplainQueryAsync(query);
Console.WriteLine(explanation);

使用 Visual Studio 的调试工具

// 在Visual Studio中使用诊断工具
// 1. 打开"诊断工具"窗口(调试 -> 窗口 -> 显示诊断工具)
// 2. 开始调试并记录性能
// 3. 分析CPU使用率、内存分配和数据库查询

// 使用条件断点调试特定查询
var products = _context.Products
    .Where(p => p.Price > 100) // 在这里设置条件断点:p.Price > 1000
    .ToList();

// 使用即时窗口测试查询
// 在调试期间,可以在即时窗口中执行:
// _context.Products.Where(p => p.Price > 100).ToList()

创建自定义性能分析中间件

// ASP.NET Core中间件,用于记录所有数据库操作
public class QueryPerformanceMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<QueryPerformanceMiddleware> _logger;

    public QueryPerformanceMiddleware(RequestDelegate next, ILogger<QueryPerformanceMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context, ApplicationDbContext dbContext)
    {
        var stopwatch = Stopwatch.StartNew();
        var originalQueryTrackingBehavior = dbContext.ChangeTracker.QueryTrackingBehavior;
        
        try
        {
            // 设置为NoTracking以提高性能,除非需要修改数据
            dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
            
            await _next(context);
        }
        finally
        {
            stopwatch.Stop();
            dbContext.ChangeTracker.QueryTrackingBehavior = originalQueryTrackingBehavior;
            
            // 记录请求处理时间和数据库查询统计
            _logger.LogInformation("请求 {Method} {Path} 处理时间: {ElapsedMs}ms, 数据库查询次数: {QueryCount}",
                context.Request.Method, context.Request.Path, stopwatch.ElapsedMilliseconds,
                dbContext.GetQueryCount());
        }
    }
}

// 扩展方法获取查询次数
public static class DbContextExtensions
{
    public static int GetQueryCount(this DbContext context)
    {
        // 这种方法可能不准确,更好的方式是使用诊断监听器
        return 0; // 实际实现需要更复杂的方法
    }
}

// 在Startup.cs中注册中间件
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMiddleware<QueryPerformanceMiddleware>();
    // ... 其他中间件
}

总结

本教程详细介绍了EF Core与MySQL的日志记录和调试技术,包括:

  1. 配置EF Core日志:

    • 基本日志配置和敏感数据记录

    • 使用ILoggerFactory集成ASP.NET Core日志系统

    • 自定义日志过滤器和结构化日志记录

  2. 查看MySQL日志:

    • 启用和配置各种MySQL日志(通用查询、慢查询、错误日志)

    • 分析MySQL日志以识别性能问题

    • 在应用程序中集成MySQL性能监控

  3. 使用调试工具分析查询性能:

    • EF Core诊断工具和监听器

    • Application Insights集成

    • MiniProfiler实时性能分析

    • JetBrains专业分析工具

    • MySQL EXPLAIN查询计划分析

    • Visual Studio调试工具

    • 自定义性能分析中间件

通过这些工具和技术,我们可以:

  • 识别和解决性能瓶颈

  • 优化数据库查询

  • 监控应用程序的健康状况

  • 诊断和解决生产环境中的问题

建议在开发环境中启用详细日志记录和性能分析,而在生产环境中则使用更保守的配置,只记录警告和错误,并定期检查慢查询日志以识别需要优化的查询。

 

EF Core系列的总结到这儿就告一段落了,后面有其他的知识点再补充,接下来计划开启RabbitMQ系列。

posted @ 2025-09-18 22:33  即兴随缘  阅读(89)  评论(0)    收藏  举报