【问题标题】:Adding a query hint when calling Table-Valued Function调用表值函数时添加查询提示
【发布时间】:2015-01-01 22:02:06
【问题描述】:

我正在从实体框架调用表值函数,并且需要能够将option (recompile) 添加到它,因为它选择的执行计划不是最佳的。在 SQL Server Management Studio 中运行查询,它看起来像这样:

select 
       * 
from dbo.fDE_myquery(0, 0, 3309, '7/1/2013', '7/1/2014', 0, 0)
option (recompile)

来自 EF,无法添加该提示,AFAIK。 EF 部分看起来像:

var query = from f in ctx.fDE_myQuery(aBool, anotherBool, StartDate, 
            EndDate, someInt, moreBool)
            select f;

我看到了这个问题:

How do I control parameter sniffing and/or query hints in entity framework?

但它已经过时了,并且公认的解决方案并没有真正提供足够的信息来说明如何实际使用实体框架实施建议的解决方案(使用计划指南)。如果这是唯一的解决方案,您如何让实体框架使用计划指南?

【问题讨论】:

    标签: sql-server entity-framework sql-server-2012 query-hints


    【解决方案1】:

    我遇到了这个:

    https://entityframework.codeplex.com/wikipage?title=Interception

    看来你可以这样做:

    public class HintInterceptor : DbCommandInterceptor
    {
        public override void ReaderExecuting(System.Data.Common.DbCommand command, DbCommandInterceptionContext<System.Data.Common.DbDataReader> interceptionContext)
        {
            command.CommandText += " option (recompile)";
            base.ReaderExecuting(command, interceptionContext);
        }
    }
    

    然后像这样注册(我是在Application_Startglobal.asax.cs 中注册的):

    DbInterception.Add(new HintInterceptor());
    

    它会让你改变CommandText。唯一的问题是它现在附加到 每个 读者查询,这可能是一个问题,因为其中一些可能会受到该提示的负面影响。我猜我可以根据上下文做一些事情来确定提示是否合适,或者更糟糕的情况我可以检查CommandText 本身。

    似乎不是最优雅或最细粒度的解决方案。

    编辑:从interceptorContext,你可以得到DbContexts,所以我定义了一个如下所示的接口:

    public interface IQueryHintContext
    {
        string QueryHint { get; set; }
        bool ApplyHint { get; set; }
    }
    

    然后创建了一个派生自我原来的 DbContext(由 EF 生成)并实现上述接口的类。然后我将拦截器更改为如下所示:

    public class HintInterceptor : DbCommandInterceptor
    {
        public override void ReaderExecuting(System.Data.Common.DbCommand command, DbCommandInterceptionContext<System.Data.Common.DbDataReader> interceptionContext)
        {
            if (interceptionContext.DbContexts.Any(db => db is Dal.IQueryHintContext))
            {
                var ctx = interceptionContext.DbContexts.First(db => db is Dal.IQueryHintContext) as Dal.IQueryHintContext;
                if (ctx.ApplyHint)
                {
                    command.CommandText += string.Format(" option ({0})", ctx.QueryHint);
                }
            }
            base.ReaderExecuting(command, interceptionContext);
        }
    }
    

    现在要使用它,我使用派生类而不是原始类创建上下文,将QueryHint 设置为我想要的任何值(在本例中为recompile)并在我执行之前设置ApplyHint命令并在之后将其设置回 false。

    为了让这一切更加独立,我最终定义了一个这样的接口:

    public interface IQueryHintContext
    {
        string QueryHint { get; set; }
        bool ApplyHint { get; set; }
    }
    

    并像这样扩展了我的数据库上下文(当然,您也可以只使用部分类来扩展 EF 生成的类):

    public class MyEntities_Ext : MyEntities, IQueryHintContext
    {
        public string QueryHint { get; set; }
        public bool ApplyHint { get; set; }
    }
    

    然后,为了让开启、关闭部分更容易处理,我定义了这个:

    public class HintScope : IDisposable
    {
        public IQueryHintContext Context { get; private set; }
        public void Dispose()
        {
            Context.ApplyHint = false;
        }
    
        public HintScope(IQueryHintContext context, string hint)
        {
            Context = context;
            Context.ApplyHint = true;
            Context.QueryHint = hint;
        }
    }
    

    现在要使用它,我可以这样做:

    using (var ctx = new MyEntities_Ext()) 
    {
        // any code that didn't need the query hint
        // ....
        // Now we want the query hint
        using (var qh = new HintScope(ctx, "recompile"))
        {
            // query that needs the recompile hint
        }
        // back to non-hint code
    }
    

    这可能有点矫枉过正,可以进一步开发(例如,使用枚举而不是字符串作为可用提示 - 或子类化 recompile 查询提示,因此您无需每次都指定字符串 recompile并冒着打字错误的风险),但它解决了我的直接问题。

    【讨论】:

    • 你能解释一下你是怎么称呼它的吗?
    • 添加 option(recompile) 需要 >10 秒的 linq 查询,我已经处理了好几天了,下降到
    • @drexdrex:我扩展了一些。希望对您有所帮助。
    • 我要特别感谢@MattBurland 提供这个干净的解决方案。我根据你的拦截器调整了我的项目,现在我对执行时间非常满意。
    • 绝妙的解决方案!!我提到它here
    【解决方案2】:

    除了您的特定用途之外,还有其他fDE_myquery 的调用者吗?多久调用一次?问题不在于您的SELECT * FROM dbo.fDE_myquery(); 获得了次优计划,而是fDE_myquery 内部的一个或多个查询获得了次优计划。因此,您可以OPTION(RECOMPILE) 添加到该 TVF 内的一个或多个查询中。

    如果这个 TVF 被称为 很多,那么这将对性能产生负面影响。这就是为什么我询问此 TVF 的其他用途:如果这是此 TVF 的唯一用途,或者到目前为止主要用途,那么如果经常采用糟糕的计划,那可能是值得的。

    但是,如果此 TVF 的其他几个调用者没有遇到问题,那么将 RECOMPILE 放入 TVF 可能不是可行的方法。不过,在这种情况下,您可以创建一个封装 SELECT * FROM dbo.fDE_myquery() OPTION (RECOMPILE); 的包装器 TVF。这似乎是一个更灵活的解决方案:)。它必须是多语句 TVF,而不是我刚刚尝试过的通常更好的内联 TVF,并且内联 TVF 似乎不喜欢 OPTION 子句,但多语句 TVF 很好。

    编辑:
    或者,如果您想纯粹在 EF 中处理这个问题,您可以简单地使用一行代码发出重新编译请求:

    ctx.context.ExecuteStoreCommand("EXEC sp_recompile 'dbo.fDE_myquery';");
    

    然后然后做你的:

    var query = from f in ctx.fDE_myQuery(aBool, anotherBool, StartDate, 
                EndDate, someInt, moreBool)
                select f;
    

    【讨论】:

    • 我确信我可以深入研究 TVF 本身(我没有编写它),甚至可以将 TVF 包装在另一个仅使用所需查询提示调用它的 TVF 中,但我认为成为最后的手段。似乎应该有一种在 EF 中指定查询提示的方法,而无需更改数据库上的内容。
    • @MattBurland :很公平。我刚刚有了另一个想法,我更新了我的答案。获得recompile 的方法不止一种:)。
    猜你喜欢
    • 1970-01-01
    • 2012-10-11
    • 1970-01-01
    • 2017-11-22
    • 2015-11-12
    • 1970-01-01
    • 2015-05-11
    • 2018-09-22
    • 1970-01-01
    相关资源
    最近更新 更多