针对特定 EF Core 查询优化 UNKNOWN
Posted
技术标签:
【中文标题】针对特定 EF Core 查询优化 UNKNOWN【英文标题】:OPTIMIZE FOR UNKNOWN on specific EF Core query 【发布时间】:2021-01-30 20:16:47 【问题描述】:我有一个使用 EF Core 3.1 在 .NET Framework 上运行的 webjob 项目。 Webjob 处理来自 Azure 服务总线的消息并将它们保存到 Azure SQL 数据库中。
我遇到的问题是 Azure SQL 数据库为 EF Core 生成的查询生成了非常糟糕的查询计划。使用生成的查询计划,执行时间为 1-2 分钟。但是,当我使用 OPTION (OPTIMIZE FOR UNKNOWN)
时,执行时间会下降到 0.01 - 0.02 分钟。
所以现在我想在 EF Core 中实现 OPTION (OPTIMIZE FOR UNKNOWN)
。我发现他们在 EF Core 3.1 中添加了一个DbCommandInterceptor
,您可以在其中将内容附加到您的查询中:MSDOCS
public class HintCommandInterceptor : DbCommandInterceptor
public override InterceptionResult<DbDataReader> ReaderExecuting(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result)
// Manipulate the command text, etc. here...
command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
return result;
但似乎这个拦截器会在每个查询上运行,我只希望它用于特定查询。 我可以为这个拦截器实现一个单独的 DbContext ,但这似乎不是一个可靠的解决方案。 有谁知道我如何以正确的方式实现这一点?
【问题讨论】:
首先你应该明白,如果使用这个查询提示,你更有可能使这个查询的整体性能变慢而不是变快。您目前正在测试的参数可能很快,但不适用于大多数查询。请看看这篇很棒的帖子brentozar.com/archive/2013/06/… 但无论如何,有一个非常好的方法:***.com/a/35104542/6696265 我知道整体性能会更差。但平均性能可能会更好,因为大型查询不再需要 1-2 分钟。 【参考方案1】:我创建了一个界面:
public interface IInterceptable
bool EnableCommandInterceptors get; set;
并在我的上下文类中实现它:
public bool EnableCommandInterceptors get; set;
在拦截器中我有:
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
if(command.CommandText.StartsWith("SELECT")
&& eventData.Context is IInterceptable intercepatable
&& intercepatable.EnableCommandInterceptors)
command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
return result;
这允许打开和关闭此功能,如果此特定查询是上下文实例将运行的唯一查询,这可能就足够了。如果没有,您可以在if(command.CommandText.StartsWith("SELECT")
部分添加更多条件。
另一种方法是使用.TagWith
标记特定查询,并在拦截器中查找标记文本:
if (command.CommandText.StartsWith("SELECT")
&& command.CommandText.Contains("my tagged text"))
command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
【讨论】:
不幸的是,上下文中有多个选择。我刚刚找到了扩展方法TagWith
也许我可以用它来区分查询?【参考方案2】:
我想追加到Gert Arnold's post...
如果您希望同步和 async 方法都起作用,例如 ToListAsync(),那么您也需要重载 Async 版本。
public class OptimizeForUnknownInterceptor : DbCommandInterceptor
public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result)
command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
return base.ReaderExecuting(command, eventData, result);
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result, CancellationToken cancellationToken = new CancellationToken())
command.CommandText += " OPTION (OPTIMIZE FOR UNKNOWN)";
return base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
我还以更有针对性的方式使用这个拦截器。所以我可以将它应用到单个上下文:
var builder = new DbContextOptionsBuilder<CustomModelContext>();
builder.AddInterceptors(new OptimizeForUnknownInterceptor());
// Includes IConfiguration for appsettings ConnectionStrings, using dependency injection
await using (var db = new CustomModelContext(builder.Options, _configuration))
...
var lst = await query.ToListAsync();
...
使用它,我为 CustomModelContext 的所有构造函数创建了一个部分:
public partial class CustomModelContext
private readonly IConfiguration _configuration;
public CustomModelContext(IConfiguration configuration)
_configuration = configuration;
public CustomModelContext(DbContextOptions<CustomModelContext> options, IConfiguration configuration)
: base(options)
_configuration = configuration;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
base.OnConfiguring(optionsBuilder);
if (!optionsBuilder.IsConfigured)
optionsBuilder.UseSqlServer(_configuration.GetConnectionString("CustomModelConnection"));
作为参考,我使用的是 .NET 5 和 EF Core 5
【讨论】:
以上是关于针对特定 EF Core 查询优化 UNKNOWN的主要内容,如果未能解决你的问题,请参考以下文章
EF Core 2.0.0 查询过滤器正在缓存 TenantId(针对 2.0.1+ 更新)