SQLSugar监控与诊断

AgenticCoding·十二月创作之星挑战赛 10w+人浏览 438人参与

引言

在现代企业级应用中,数据库性能直接影响着系统的响应速度和用户体验。SQLSugar作为一款优秀的.NET ORM框架,不仅需要掌握其基本使用方法,更需要建立完整的监控与诊断体系,确保在复杂业务场景下能够快速定位和解决性能问题。本文将全面介绍SQLSugar的监控与诊断方案,帮助开发者构建高效的数据库性能管理体系。

一、SQLSugar内置监控机制

1.1 AOP事件监控体系

SQLSugar提供了完整的AOP(面向切面编程)事件机制,能够在不侵入业务代码的情况下实现全面的SQL监控。

public class SqlSugarAopLogger
{
    public static void ConfigureAop(SqlSugarClient db)
    {
        // 开启SQL执行监控
        db.Aop.OnLogExecuting = (sql, pars) =>
        {
            // 1. 记录原始SQL
            string originalSql = sql;
            
            // 2. 格式化参数
            SugarParameter[] parameters = pars as SugarParameter[];
            string formattedSql = FormatSql(originalSql, parameters);
            
            // 3. 记录执行前日志
            LogSql("SQL执行前", formattedSql, db.Ado.Connection.Database);
        };

        // SQL执行完成事件
        db.Aop.OnLogExecuted = (sql, pars) =>
        {
            // 可以在此处记录执行完成状态
            Console.WriteLine($"SQL执行完成: {sql}");
        };

        // SQL执行错误事件
        db.Aop.OnError = (exp) =>
        {
            // 记录错误信息
            LogError($"SQL执行错误: {exp.Message}", exp.Sql, db.Ado.Connection.Database);
            
            // 可以集成到错误监控系统
            SendToErrorMonitoringSystem(exp);
        };

        // 执行时间监控
        db.Aop.OnExecutingChangeSql = (sql, pars) =>
        {
            // 可以修改SQL语句
            return new KeyValuePair<string, SugarParameter[]>(sql, pars);
        };
    }

    private static string FormatSql(string sql, SugarParameter[] parameters)
    {
        if (parameters == null || parameters.Length == 0)
            return sql;

        foreach (var param in parameters)
        {
            string paramValue = param.Value?.ToString() ?? "NULL";
            sql = sql.Replace(param.ParameterName, $"'{paramValue.Replace("'", "''")}'");
        }
        
        return sql;
    }
}

1.2 执行统计与性能分析

public class SqlPerformanceMonitor
{
    private readonly ConcurrentDictionary<string, SqlExecutionStats> _stats =
        new ConcurrentDictionary<string, SqlExecutionStats>();

    public void ConfigurePerformanceMonitor(SqlSugarClient db)
    {
        Stopwatch stopwatch = new Stopwatch();
        
        db.Aop.OnLogExecuting = (sql, pars) =>
        {
            stopwatch.Restart();
        };

        db.Aop.OnLogExecuted = (sql, pars) =>
        {
            stopwatch.Stop();
            long elapsedMs = stopwatch.ElapsedMilliseconds;
            
            // 生成SQL指纹(去除具体参数值)
            string sqlFingerprint = GenerateSqlFingerprint(sql);
            
            // 更新统计信息
            var stats = _stats.GetOrAdd(sqlFingerprint, 
                key => new SqlExecutionStats { SqlTemplate = sqlFingerprint });
            
            stats.TotalExecutions++;
            stats.TotalDurationMs += elapsedMs;
            stats.AverageDurationMs = stats.TotalDurationMs / stats.TotalExecutions;
            
            if (elapsedMs > stats.MaxDurationMs)
                stats.MaxDurationMs = elapsedMs;
                
            if (stats.MinDurationMs == 0 || elapsedMs < stats.MinDurationMs)
                stats.MinDurationMs = elapsedMs;
            
            // 记录慢查询
            if (elapsedMs > 1000) // 超过1秒定义为慢查询
            {
                LogSlowQuery(sql, pars, elapsedMs, db.Ado.Connection.Database);
            }
        };
    }

    public List<SqlExecutionStats> GetPerformanceReport()
    {
        return _stats.Values
            .OrderByDescending(s => s.TotalDurationMs)
            .Take(20) // 返回耗时最多的20个SQL
            .ToList();
    }
}

public class SqlExecutionStats
{
    public string SqlTemplate { get; set; }
    public long TotalExecutions { get; set; }
    public long TotalDurationMs { get; set; }
    public long AverageDurationMs { get; set; }
    public long MaxDurationMs { get; set; }
    public long MinDurationMs { get; set; }
}

二、集成第三方监控系统

2.1 集成MiniProfiler

public static class MiniProfilerIntegration
{
    public static void ConfigureMiniProfiler(SqlSugarClient db)
    {
        db.Aop.OnLogExecuting = (sql, pars) =>
        {
            var profiler = MiniProfiler.Current;
            if (profiler != null)
            {
                // 创建MiniProfiler自定义步骤
                var stepKey = $"sql_{Guid.NewGuid()}";
                var step = profiler.CustomTiming("sql", "执行SQL", sql);
                
                // 将step存储在AsyncLocal中,以便在执行完成后结束
                CurrentProfilerStep.Value = step;
                
                // 添加SQL参数信息
                if (pars is SugarParameter[] parameters && parameters.Length > 0)
                {
                    var paramInfo = string.Join(", ", 
                        parameters.Select(p => $"{p.ParameterName}={p.Value}"));
                    step.CommandString = $"{sql} -- 参数: {paramInfo}";
                }
            }
        };

        db.Aop.OnLogExecuted = (sql, pars) =>
        {
            var step = CurrentProfilerStep.Value;
            if (step != null)
            {
                step?.Dispose();
                CurrentProfilerStep.Value = null;
            }
        };

        db.Aop.OnError = (exp) =>
        {
            var step = CurrentProfilerStep.Value;
            if (step != null)
            {
                step.Errored = true;
                step.Dispose();
                CurrentProfilerStep.Value = null;
            }
        };
    }

    private static readonly AsyncLocal<CustomTiming> CurrentProfilerStep = 
        new AsyncLocal<CustomTiming>();
}

2.2 集成Application Insights

public class ApplicationInsightsIntegration
{
    private readonly TelemetryClient _telemetryClient;

    public ApplicationInsightsIntegration(TelemetryClient telemetryClient)
    {
        _telemetryClient = telemetryClient;
    }

    public void ConfigureAppInsights(SqlSugarClient db)
    {
        db.Aop.OnLogExecuting = (sql, pars) =>
        {
            var operationHolder = _telemetryClient.StartOperation<DependencyTelemetry>("SQL");
            operationHolder.Telemetry.Type = "SQL";
            operationHolder.Telemetry.Target = db.Ado.Connection.Database;
            operationHolder.Telemetry.Data = sql;
            
            CurrentOperation.Value = operationHolder;
        };

        db.Aop.OnLogExecuted = (sql, pars) =>
        {
            var operation = CurrentOperation.Value;
            if (operation != null)
            {
                _telemetryClient.StopOperation(operation);
                CurrentOperation.Value = null;
            }
        };

        db.Aop.OnError = (exp) =>
        {
            var operation = CurrentOperation.Value;
            if (operation != null)
            {
                operation.Telemetry.Success = false;
                operation.Telemetry.Properties["ErrorMessage"] = exp.Message;
                _telemetryClient.TrackException(exp);
                _telemetryClient.StopOperation(operation);
                CurrentOperation.Value = null;
            }
        };
    }

    private static readonly AsyncLocal<IOperationHolder<DependencyTelemetry>> CurrentOperation = 
        new AsyncLocal<IOperationHolder<DependencyTelemetry>>();
}

三、慢查询分析与优化

3.1 慢查询自动捕获系统

public class SlowQueryMonitor
{
    private readonly List<SlowQueryRecord> _slowQueries = new List<SlowQueryRecord>();
    private readonly object _lock = new object();
    private readonly int _slowThresholdMs;

    public SlowQueryMonitor(int slowThresholdMs = 1000)
    {
        _slowThresholdMs = slowThresholdMs;
    }

    public void ConfigureSlowQueryDetection(SqlSugarClient db)
    {
        Stopwatch stopwatch = new Stopwatch();
        string currentSql = string.Empty;
        SugarParameter[] currentParams = null;

        db.Aop.OnLogExecuting = (sql, pars) =>
        {
            currentSql = sql;
            currentParams = pars as SugarParameter[];
            stopwatch.Restart();
        };

        db.Aop.OnLogExecuted = (sql, pars) =>
        {
            stopwatch.Stop();
            long elapsedMs = stopwatch.ElapsedMilliseconds;

            if (elapsedMs > _slowThresholdMs)
            {
                var record = new SlowQueryRecord
                {
                    Sql = currentSql,
                    Parameters = currentParams,
                    ExecutionTime = DateTime.Now,
                    DurationMs = elapsedMs,
                    Database = db.Ado.Connection.Database,
                    ConnectionString = db.Ado.Connection.ConnectionString,
                    StackTrace = GetRelevantStackTrace()
                };

                lock (_lock)
                {
                    _slowQueries.Add(record);
                    // 保持最近的1000条慢查询记录
                    if (_slowQueries.Count > 1000)
                        _slowQueries.RemoveAt(0);
                }

                // 异步通知管理员
                Task.Run(() => NotifyAdministrator(record));
            }
        };
    }

    public List<SlowQueryRecord> GetSlowQueries(DateTime? from = null, DateTime? to = null)
    {
        lock (_lock)
        {
            var query = _slowQueries.AsQueryable();
            
            if (from.HasValue)
                query = query.Where(q => q.ExecutionTime >= from.Value);
                
            if (to.HasValue)
                query = query.Where(q => q.ExecutionTime <= to.Value);
                
            return query.OrderByDescending(q => q.DurationMs).ToList();
        }
    }

    private string GetRelevantStackTrace()
    {
        var stackTrace = new System.Diagnostics.StackTrace(true);
        var frames = stackTrace.GetFrames();
        
        // 只保留与业务代码相关的堆栈信息
        var relevantFrames = frames
            .Where(f => !f.GetMethod().DeclaringType.FullName.StartsWith("System") &&
                       !f.GetMethod().DeclaringType.FullName.StartsWith("Microsoft") &&
                       !f.GetMethod().DeclaringType.FullName.Contains("SqlSugar"))
            .Take(5) // 只取前5个相关帧
            .Select(f => $"{f.GetMethod().DeclaringType.FullName}.{f.GetMethod().Name}:{f.GetFileLineNumber()}");
            
        return string.Join(Environment.NewLine, relevantFrames);
    }
}

public class SlowQueryRecord
{
    public string Sql { get; set; }
    public SugarParameter[] Parameters { get; set; }
    public DateTime ExecutionTime { get; set; }
    public long DurationMs { get; set; }
    public string Database { get; set; }
    public string ConnectionString { get; set; }
    public string StackTrace { get; set; }
}

3.2 慢查询分析报表

public class SlowQueryAnalyzer
{
    public SlowQueryReport AnalyzeQueries(List<SlowQueryRecord> queries)
    {
        var report = new SlowQueryReport
        {
            TotalSlowQueries = queries.Count,
            AnalysisTime = DateTime.Now
        };

        if (queries.Count == 0)
            return report;

        // 按SQL模式分组(去除具体参数值)
        var sqlGroups = queries
            .GroupBy(q => NormalizeSql(q.Sql))
            .Select(g => new SqlPatternStats
            {
                SqlPattern = g.Key,
                ExecutionCount = g.Count(),
                AverageDuration = g.Average(q => q.DurationMs),
                MaxDuration = g.Max(q => q.DurationMs),
                LastExecution = g.Max(q => q.ExecutionTime)
            })
            .OrderByDescending(s => s.MaxDuration)
            .ToList();

        report.SqlPatterns = sqlGroups;
        report.MostFrequentPattern = sqlGroups.OrderByDescending(s => s.ExecutionCount).FirstOrDefault();
        report.SlowestPattern = sqlGroups.OrderByDescending(s => s.MaxDuration).FirstOrDefault();

        // 时间分布分析
        var hourlyDistribution = queries
            .GroupBy(q => q.ExecutionTime.Hour)
            .Select(g => new { Hour = g.Key, Count = g.Count() })
            .OrderBy(x => x.Hour)
            .ToList();

        report.PeakHours = hourlyDistribution
            .OrderByDescending(x => x.Count)
            .Take(3)
            .Select(x => x.Hour)
            .ToList();

        return report;
    }

    private string NormalizeSql(string sql)
    {
        // 去除具体参数值,只保留SQL模式
        // 这是一个简化的实现,实际中可能需要更复杂的处理
        string pattern = @"@\w+\d*";
        return System.Text.RegularExpressions.Regex.Replace(sql, pattern, "@param");
    }
}

public class SlowQueryReport
{
    public int TotalSlowQueries { get; set; }
    public DateTime AnalysisTime { get; set; }
    public List<SqlPatternStats> SqlPatterns { get; set; }
    public SqlPatternStats MostFrequentPattern { get; set; }
    public SqlPatternStats SlowestPattern { get; set; }
    public List<int> PeakHours { get; set; }
}

public class SqlPatternStats
{
    public string SqlPattern { get; set; }
    public int ExecutionCount { get; set; }
    public double AverageDuration { get; set; }
    public double MaxDuration { get; set; }
    public DateTime LastExecution { get; set; }
}

四、连接池监控与优化

4.1 连接池状态监控

public class ConnectionPoolMonitor
{
    private readonly SqlSugarClient _db;
    private readonly Timer _monitorTimer;
    private readonly List<ConnectionPoolMetrics> _metricsHistory = new List<ConnectionPoolMetrics>();

    public ConnectionPoolMonitor(SqlSugarClient db, int monitorIntervalSeconds = 30)
    {
        _db = db;
        _monitorTimer = new Timer(CollectMetrics, null, 
            TimeSpan.Zero, TimeSpan.FromSeconds(monitorIntervalSeconds));
    }

    private void CollectMetrics(object state)
    {
        try
        {
            var metrics = GetConnectionPoolMetrics();
            _metricsHistory.Add(metrics);
            
            // 保留最近24小时的数据
            var cutoffTime = DateTime.Now.AddHours(-24);
            _metricsHistory.RemoveAll(m => m.Timestamp < cutoffTime);
            
            // 检查连接池健康状况
            CheckConnectionPoolHealth(metrics);
        }
        catch (Exception ex)
        {
            Console.WriteLine($"连接池监控异常: {ex.Message}");
        }
    }

    private ConnectionPoolMetrics GetConnectionPoolMetrics()
    {
        // 注意:不同数据库的连接池监控方式不同
        // 这里以SQL Server为例
        var metrics = new ConnectionPoolMetrics
        {
            Timestamp = DateTime.Now,
            Database = _db.Ado.Connection.Database
        };

        // 在实际应用中,可能需要执行特定的系统视图查询
        // 例如SQL Server: SELECT * FROM sys.dm_exec_connections
        
        return metrics;
    }

    public ConnectionPoolDiagnosis GetDiagnosis()
    {
        if (_metricsHistory.Count < 10)
            return new ConnectionPoolDiagnosis { Status = "数据不足" };

        var recentMetrics = _metricsHistory
            .OrderByDescending(m => m.Timestamp)
            .Take(100)
            .ToList();

        var diagnosis = new ConnectionPoolDiagnosis
        {
            Status = "正常",
            Suggestions = new List<string>()
        };

        // 分析连接数趋势
        var avgConnections = recentMetrics.Average(m => m.ActiveConnections);
        var maxConnections = recentMetrics.Max(m => m.ActiveConnections);
        
        if (avgConnections > 100)
        {
            diagnosis.Status = "警告";
            diagnosis.Suggestions.Add($"平均连接数较高: {avgConnections:F0},考虑优化连接管理");
        }
        
        if (maxConnections > 150)
        {
            diagnosis.Status = "警告";
            diagnosis.Suggestions.Add($"最大连接数过高: {maxConnections:F0},可能存在连接泄露");
        }

        return diagnosis;
    }
}

public class ConnectionPoolMetrics
{
    public DateTime Timestamp { get; set; }
    public string Database { get; set; }
    public int ActiveConnections { get; set; }
    public int IdleConnections { get; set; }
    public int ConnectionTimeouts { get; set; }
    public double AvgConnectionWaitTime { get; set; }
}

public class ConnectionPoolDiagnosis
{
    public string Status { get; set; }
    public List<string> Suggestions { get; set; }
}

五、实时性能仪表板

5.1 Web监控界面

<!-- PerformanceDashboard.html -->
<!DOCTYPE html>
<html>
<head>
    <title>SQLSugar性能监控仪表板</title>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <style>
        .dashboard {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
            padding: 20px;
        }
        .metric-card {
            background: white;
            border-radius: 8px;
            padding: 20px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        .slow-query-list {
            max-height: 400px;
            overflow-y: auto;
        }
    </style>
</head>
<body>
    <div class="dashboard">
        <!-- QPS监控 -->
        <div class="metric-card">
            <h3>QPS监控</h3>
            <canvas id="qpsChart"></canvas>
        </div>
        
        <!-- 慢查询统计 -->
        <div class="metric-card">
            <h3>慢查询统计</h3>
            <canvas id="slowQueryChart"></canvas>
        </div>
        
        <!-- 连接池状态 -->
        <div class="metric-card">
            <h3>连接池状态</h3>
            <div id="connectionPoolStatus"></div>
        </div>
        
        <!-- 最近慢查询 -->
        <div class="metric-card">
            <h3>最近慢查询</h3>
            <div id="recentSlowQueries" class="slow-query-list"></div>
        </div>
    </div>
    
    <script>
        // 实时数据更新
        async function updateDashboard() {
            const response = await fetch('/api/sqlsugar-metrics');
            const data = await response.json();
            
            updateQPSChart(data.qpsData);
            updateSlowQueryChart(data.slowQueryData);
            updateConnectionPool(data.connectionPool);
            updateSlowQueries(data.recentSlowQueries);
        }
        
        setInterval(updateDashboard, 5000);
        updateDashboard(); // 初始加载
    </script>
</body>
</html>

SQLSugar监控与诊断体系的建立是一个系统性工程,需要从多个层面进行考虑:

  1. 监控层面:利用SQLSugar的AOP机制实现全面的SQL监控

  2. 分析层面:建立慢查询分析、性能统计和连接池监控

  3. 可视化层面:通过仪表板实时展示关键指标

  4. 告警层面:建立智能告警机制,及时发现潜在问题

  5. 集成层面:与现有监控系统(如Application Insights、MiniProfiler)集成

在实际应用中,建议根据以下原则进行配置:

  • 分级监控:根据环境(开发、测试、生产)配置不同级别的监控

  • 性能开销:监控本身会带来性能开销,需要权衡监控粒度和性能影响

  • 自动化处理:对常见问题建立自动化处理机制

  • 持续优化:定期分析监控数据,持续优化SQL和数据库设计

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code_shenbing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值