如何获取并记录所有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
相关产品推荐
相关产品推荐

