You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何获取并记录所有Linq To Db查询及超时查询的执行计划至数据库表?

实现Linq To DB查询监控与执行计划采集

统一配置方案:使用LinqToDB命令拦截器

LinqToDB提供的ICommandInterceptor接口是全局统一处理所有查询的核心扩展点,无需修改每个查询的代码,只需注册一次即可覆盖应用内所有LinqToDB操作。

步骤1:实现自定义命令拦截器

创建一个实现ICommandInterceptor的类,通过重写关键方法完成查询记录和执行计划采集:

public class QueryMonitoringInterceptor : ICommandInterceptor
{
    private readonly DataConnection _logDb;

    public QueryMonitoringInterceptor(DataConnection logDb)
    {
        _logDb = logDb;
    }

    // 记录查询执行开始时间
    public void BeforeExecute(IDataContext context, CommandEventData eventData)
    {
        eventData.Command.Tag = Stopwatch.StartNew();
    }

    // 查询执行完成后处理日志记录与慢查询计划采集
    public void AfterExecute(IDataContext context, CommandEventData eventData)
    {
        var stopwatch = (Stopwatch)eventData.Command.Tag;
        stopwatch.Stop();
        var executionTime = stopwatch.Elapsed;

        // 1. 记录所有查询到数据库表
        var queryLog = new QueryLog
        {
            QueryText = eventData.Command.CommandText,
            ExecutionStartTime = DateTime.UtcNow.Subtract(executionTime),
            ExecutionDurationMs = executionTime.TotalMilliseconds,
            DatabaseProvider = context.DataProvider.Name
        };
        _logDb.Insert(queryLog);

        // 2. 采集执行时长超2分钟的查询计划
        if (executionTime.TotalMinutes >= 2)
        {
            string executionPlan = GetExecutionPlan(context, eventData.Command.CommandText);
            if (!string.IsNullOrEmpty(executionPlan))
            {
                var slowQueryLog = new SlowQueryLog
                {
                    QueryLogId = queryLog.Id,
                    ExecutionPlan = executionPlan,
                    CreatedAt = DateTime.UtcNow
                };
                _logDb.Insert(slowQueryLog);
            }
        }
    }

    // 根据数据库类型适配执行计划获取逻辑
    private string GetExecutionPlan(IDataContext context, string queryText)
    {
        string plan = null;
        switch (context.DataProvider.Name)
        {
            case "SqlServer":
                using var tempConn = new SqlServerDataConnection(context.Connection.ConnectionString);
                tempConn.Execute("SET SHOWPLAN_XML ON");
                plan = tempConn.Query<string>(queryText).FirstOrDefault();
                tempConn.Execute("SET SHOWPLAN_XML OFF");
                break;
            case "PostgreSQL":
                using var pgConn = new PostgresDataConnection(context.Connection.ConnectionString);
                plan = pgConn.Query<string>($"EXPLAIN (ANALYZE, FORMAT XML) {queryText}").FirstOrDefault();
                break;
            case "MySql":
                using var mysqlConn = new MySqlDataConnection(context.Connection.ConnectionString);
                plan = mysqlConn.Query<string>($"EXPLAIN FORMAT=JSON {queryText}").FirstOrDefault();
                break;
            // 其他数据库类型可按需扩展
        }
        return plan;
    }

    // 接口其他方法默认实现
    public void OnError(IDataContext context, CommandEventData eventData, Exception exception) { }
    public void BeforeExecuteAsync(IDataContext context, CommandEventData eventData, CancellationToken cancellationToken)
    {
        BeforeExecute(context, eventData);
    }
    public async Task AfterExecuteAsync(IDataContext context, CommandEventData eventData, CancellationToken cancellationToken)
    {
        AfterExecute(context, eventData);
        await Task.CompletedTask;
    }
    public async Task OnErrorAsync(IDataContext context, CommandEventData eventData, Exception exception, CancellationToken cancellationToken)
    {
        OnError(context, eventData, exception);
        await Task.CompletedTask;
    }
}

步骤2:注册拦截器到全局配置

在应用启动时,将自定义拦截器注册到LinqToDB全局配置,或针对单个DataConnection实例添加:

// 全局注册(覆盖所有DataConnection实例)
DataConnection.GlobalOptions.Interceptors.Add(new QueryMonitoringInterceptor(logDbConnection));

// 或针对单个DataConnection实例注册
var db = new SqlServerDataConnection(connectionString);
db.Interceptors.Add(new QueryMonitoringInterceptor(logDbConnection));

关键注意事项

  • 数据库兼容性:不同数据库获取执行计划的语法差异较大,需根据实际使用的数据库类型适配GetExecutionPlan方法。
  • 性能控制:采集执行计划需要额外执行一次查询,仅针对超2分钟的慢查询处理可避免不必要的性能开销。
  • 连接隔离:获取执行计划时建议使用独立连接,避免修改原查询的连接上下文(如SQL Server的SET SHOWPLAN_XML会改变连接行为)。
  • 日志表设计:需提前创建存储日志的表,示例SQL Server表结构:
CREATE TABLE QueryLog (
    Id INT IDENTITY PRIMARY KEY,
    QueryText NVARCHAR(MAX) NOT NULL,
    ExecutionStartTime DATETIME2 NOT NULL,
    ExecutionDurationMs DECIMAL(18,2) NOT NULL,
    DatabaseProvider NVARCHAR(50) NOT NULL,
    CreatedAt DATETIME2 DEFAULT GETUTCDATE()
);

CREATE TABLE SlowQueryLog (
    Id INT IDENTITY PRIMARY KEY,
    QueryLogId INT FOREIGN KEY REFERENCES QueryLog(Id),
    ExecutionPlan XML NOT NULL,
    CreatedAt DATETIME2 DEFAULT GETUTCDATE()
);

内容的提问来源于stack exchange,提问作者Scott

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 08:42:57