针对特定 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

EF Core性能优化

EF Core 标头-详细信息查询优化

EF Core 2.0.0 查询过滤器正在缓存 TenantId(针对 2.0.1+ 更新)

EF Core 查询优化:我可以在 List<string> 上使用 Contains 吗?

从查询 C#、EF Core 返回一个值