복붙노트

[SQL] 6 파라미터 스니핑 IF

SQL

6 파라미터 스니핑 IF

여기 넣어 너무 큰 동적 쿼리가 있습니다. 안전이 현재 형태의에서 검색 매개 변수의 수는 전달에 따라 조인 동적으로 빌드에 대한 CLR 절차를 이용하여 말을 그 결과를 받아 다시 가져 것은 최종 사용자에게 중요한 속성에 더 테이블을 설명하기 위해 섭니다. 나는 엔티티에 LINQ에 전체 쿼리를 변환 내가 발견 한 것은 생산하는 SQL이 일을 할 수있는 효율적인 충분한 그러나 EF 6, 쿼리 timesout를 통해 실행 있다는 것이다있다. SSMS에서 그것을 결과 SQL을 복용하고 실행하는 3 이하 초에서 실행됩니다. 난 단지 내 문제가 스니핑 매개 변수는 것을 상상할 수있다. 나는 데이터베이스의 모든 테이블에 업데이트 통계를 시도하고이 문제를 해결하지 않았습니다.

내 질문은 :

어떻게 든 EF를 통해 "옵션 RECOMPILE"와 같은 옵션을 포함 할 수 있습니까?

해결법

  1. ==============================

    1.이 명령의 끝에 옵션 (재 컴파일)를 추가 예를 들어, DB에 그들을 실행하기 전에 내부 SQL 명령을 조작 할 수 EF6의 차단 기능을 사용하는 것이 가능하다 :

    이 명령의 끝에 옵션 (재 컴파일)를 추가 예를 들어, DB에 그들을 실행하기 전에 내부 SQL 명령을 조작 할 수 EF6의 차단 기능을 사용하는 것이 가능하다 :

    public class OptionRecompileHintDbCommandInterceptor : IDbCommandInterceptor
    {
        public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<Int32> interceptionContext)
        {
        }
    
        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
        }
    
        public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
        }
    
        public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            addQueryHint(command);
        }
    
        public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
        }
    
        public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            addQueryHint(command);
        }
    
        private static void addQueryHint(IDbCommand command)
        {
            if (command.CommandType != CommandType.Text || !(command is SqlCommand))
                return;
    
            if (command.CommandText.StartsWith("select", StringComparison.OrdinalIgnoreCase) && !command.CommandText.Contains("option(recompile)"))
            {
                command.CommandText = command.CommandText + " option(recompile)";
            }
        }
    }
    

    그것을 사용하려면 응용 프로그램의 시작 부분에 다음 줄을 추가합니다 :

    DbInterception.Add(new OptionRecompileHintDbCommandInterceptor());
    
  2. ==============================

    2.나는 VahidN의 솔루션처럼, 그에게 투표를 할 수 있지만, 나는 그것이 일어날 때 더 제어 할 수 있습니다. 그것은 DB 인터셉터는 매우 전역 밝혀, 나는 단지이 특정 시나리오에서 특정 컨텍스트에서 발생하고 싶었다.

    나는 VahidN의 솔루션처럼, 그에게 투표를 할 수 있지만, 나는 그것이 일어날 때 더 제어 할 수 있습니다. 그것은 DB 인터셉터는 매우 전역 밝혀, 나는 단지이 특정 시나리오에서 특정 컨텍스트에서 발생하고 싶었다.

    여기에서 우리는 또한에 원하는대로 해제 할 수있는 다른 쿼리 힌트를 추가 지원하기 위해 접지 작업을 설정하고 있습니다.

    나는 종종 연결 문자열을 전달하는 방법을 노출하기 때문에, 나는 또한에 대한 지원이 포함되어 있습니다.

    다음은 컨텍스트를 활성화 / 부분 클래스 EF 생성 확장하여, 프로그램 적 힌트를 해제하는 플래그를 줄 것이다. 우리는 또한 자신의 방법으로 인터셉터에서 재사용 코드의 작은 조각을 던졌다.

    작은 인터페이스

    public interface IQueryHintable
    {
        bool HintWithRecompile { get; set; }
    }
    

    DB 명령 인터셉터

    public class OptionHintDbCommandInterceptor : IDbCommandInterceptor
    {
        public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<Int32> interceptionContext)
        {
            AddHints(command, interceptionContext);
        }
    
        public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
        {
        }
    
        public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
        }
    
        public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            AddHints(command, interceptionContext);
        }
    
        public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
        }
    
        public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
        {
            AddHints(command, interceptionContext);
        }
    
        private static void AddHints<T>(DbCommand command, DbCommandInterceptionContext<T> interceptionContext)
        {
            var context = interceptionContext.DbContexts.FirstOrDefault();
            if (context is IQueryHintable)
            {
                var hints = (IQueryHintable)context;
    
                if (hints.HintWithRecompile)
                {
                    addRecompileQueryHint(command);
                }
            }
        }
    
        private static void addRecompileQueryHint(IDbCommand command)
        {
            if (command.CommandType != CommandType.Text || !(command is SqlCommand))
                return;
    
            if (command.CommandText.StartsWith("select", StringComparison.OrdinalIgnoreCase) && !command.CommandText.Contains("option(recompile)"))
            {
                command.CommandText = command.CommandText + " option(recompile)";
            }
        }
    }
    

    엔터티 컨텍스트를 확장하는 것은 IQueryHintable를 추가하는 방법

    public partial class SomeEntities : DbContext, IQueryHintable
    {
        public bool HintWithRecompile { get; set; }
    
        public SomeEntities (string connectionString, bool hintWithRecompile) : base(connectionString)
        {
            HintWithRecompile = hintWithRecompile;
        }
    
        public SomeEntities (bool hintWithRecompile) : base()
        {
            HintWithRecompile = hintWithRecompile;
        }
    
        public SomeEntities (string connectionString) : base(connectionString)
        {
        }
    
    }
    

    등록 DB 명령 인터셉터 (Global.asax에)

        DbInterception.Add(new OptionHintDbCommandInterceptor());
    

    다양한 컨텍스트를 사용

        using(var db = new SomeEntities(hintWithRecompile: true) )
        {
        }
    

    켜기 또는 끄기

        db.HintWithRecompile = true;
        // Do Something
        db.HintWithRecompile = false;
    

    당신은 또한 HintOptimizeForUnknown, 또는 다른 쿼리 힌트를 구현 할 수 있습니다 때문에,이 HintWithRecompile을했다.

  3. ==============================

    3.내가 쿼리하기 위해 할 수있는 일시적인 추가 옵션 (재 컴파일)을 OptionRecompileScope 내에서 실행하는 것이이 작은 유틸리티 클래스를 썼다 그래서 나를 위해 동일은 @Greg에 관해서는, 폭이 시스템을 가능하게하는 것은 옵션이 아니었다.

    내가 쿼리하기 위해 할 수있는 일시적인 추가 옵션 (재 컴파일)을 OptionRecompileScope 내에서 실행하는 것이이 작은 유틸리티 클래스를 썼다 그래서 나를 위해 동일은 @Greg에 관해서는, 폭이 시스템을 가능하게하는 것은 옵션이 아니었다.

    사용 예제

    using (new OptionRecompileScope(dbContext))
    {
        return dbContext.YourEntities.Where(<YourExpression>).ToList();
    }
    

    이행

    public class OptionRecompileScope : IDisposable
    {
        private readonly OptionRecompileDbCommandInterceptor interceptor;
    
        public OptionRecompileScope(DbContext context)
        {
            interceptor = new OptionRecompileDbCommandInterceptor(context);
            DbInterception.Add(interceptor);
        }
    
        public void Dispose()
        {
            DbInterception.Remove(interceptor);
        }
    
        private class OptionRecompileDbCommandInterceptor : IDbCommandInterceptor
        {
            private readonly DbContext dbContext;
    
            internal OptionRecompileDbCommandInterceptor(DbContext dbContext)
            {
                this.dbContext = dbContext;
            }
    
            public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
            {
            }
    
            public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
            {
            }
    
            public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
            {
                if (ShouldIntercept(command, interceptionContext))
                {
                    AddOptionRecompile(command);
                }
            }
    
            public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
            {
            }
    
            public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
            {
                if (ShouldIntercept(command, interceptionContext))
                {
                    AddOptionRecompile(command);
                }
            }
    
            public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
            {
            }
    
            private static void AddOptionRecompile(IDbCommand command)
            {
                command.CommandText = command.CommandText + " option(recompile)";
            }
    
            private bool ShouldIntercept(IDbCommand command, DbCommandInterceptionContext interceptionContext)
            {
                return 
                    command.CommandType == CommandType.Text &&
                    command is SqlCommand &&
                    interceptionContext.DbContexts.Any(interceptionDbContext => ReferenceEquals(interceptionDbContext, dbContext));
            }
        }
    }
    
  4. ==============================

    4.나는 비슷한 문제가 있었다. 결국,이 명령을 사용하여 캐시 된 쿼리 계획을 제거 :

    나는 비슷한 문제가 있었다. 결국,이 명령을 사용하여 캐시 된 쿼리 계획을 제거 :

    dbcc freeproccache([your plan handle here])
    

    계획 핸들을 얻기 위해서는 다음과 같은 쿼리를 사용할 수 있습니다 :

    SELECT qs.plan_handle, a.attrlist, est.dbid, text
    FROM   sys.dm_exec_query_stats qs
    CROSS  APPLY sys.dm_exec_sql_text(qs.sql_handle) est
    CROSS  APPLY (SELECT epa.attribute + '=' + convert(nvarchar(127), epa.value) + '   '
          FROM   sys.dm_exec_plan_attributes(qs.plan_handle) epa
          WHERE  epa.is_cache_key = 1
          ORDER  BY epa.attribute
          FOR    XML PATH('')) AS a(attrlist)
     WHERE  est.text LIKE '%standardHourRate%' and est.text like '%q__7%'and est.text like '%Unit Overhead%'
     AND  est.text NOT LIKE '%sys.dm_exec_plan_attributes%'
    

    쿼리의 적절한 조각 조항 '등'의 내용을 교체.

    당신은 내 모든 문제에서 볼 수있다 :

    엔티티 프레임 워크가 느리게 실행하여 SQL 쿼리, 나쁜 쿼리 계획을 사용

  5. ==============================

    5.EF 코어 2에서 유사한 경우가 있었다, 그러나 그것은 단지 인터셉터 구현에 차이가있다. 이 스레드 나에게 가장 도움이 때문에, 나는 영업 이익은 EF 6 요청하는 경우에도, 당신과 함께 내 구현을 공유하고자합니다. 내가 @Oskar 베르그와 @Greg 솔루션 조금 개선 Furthemore, 재 컴파일 옵션을 확장해야하는 쿼리를 골라합니다.

    EF 코어 2에서 유사한 경우가 있었다, 그러나 그것은 단지 인터셉터 구현에 차이가있다. 이 스레드 나에게 가장 도움이 때문에, 나는 영업 이익은 EF 6 요청하는 경우에도, 당신과 함께 내 구현을 공유하고자합니다. 내가 @Oskar 베르그와 @Greg 솔루션 조금 개선 Furthemore, 재 컴파일 옵션을 확장해야하는 쿼리를 골라합니다.

    EF 코어 2에서 인터셉터가 좀 까다 롭고 조금 다르다.

    그것은 패키지 Microsoft.Extensions.DiagnosticAdapter 다음과 같은 코드를 통해 구현 될 수

    var contextDblistener = this.contextDb.GetService<DiagnosticSource>();
    (contextDblistener as DiagnosticListener).SubscribeWithAdapter(new SqlCommandListener());
    

    인터셉터 자체는 해당 DiagnosticName 주석 표시의 방법이 필요하다.

    내가 인터셉터에 준 팅겨는 원하는 옵션을 확장해야하는 쿼리 밖으로 하나의 명령 안에 특정 태그 (SQL 주석)를 찾는 것을이었다.

    재 컴파일 옵션을 사용하는 쿼리를 표시하려면, 당신은 단순히 false로 다시 true로 부울 설정으로 주위를 성가 시게하고없이 쿼리에 .TagWith (Constants.SQL_TAG_QUERYHINT_RECOMPILE)를 추가해야합니다.

    이 방법 당신은 또한 병렬 쿼리가 차단되는 모든 때문에 하나의 부울 HintWithRecompile의 재 컴파일 옵션으로 확대되고 문제가 없습니다.

    상수 태그 문자열은 그들이 단지 내부의 SQL 주석이 아닌 쿼리 자체의 일부가 될 수 있도록 설계되었습니다. 전체 SQL 명령을 분석하고 쿼리의 일부 텍스트 안에 당신의 깃발을 일치하기 때문에 재 컴파일을 추가하지 않도록 나는 단지 태그 부분을 (EF의 구현 세부)을 분석 할 수있는 해결책을 찾기 could't.

    은 "최적화 알 수없는 내용은"일부는 명령 매개 변수 속성을 사용하여 더욱 향상시킬 수 있습니다,하지만 난 당신에게 그를 떠날거야.

    public class SqlCommandListener
    {
        [DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuting")]
        public void OnCommandExecuting(DbCommand command, DbCommandMethod executeMethod, Guid commandId, Guid connectionId, bool async, DateTimeOffset startTime)
        {
            AddQueryHintsBasedOnTags(command);
        }
    
        [DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandExecuted")]
        public void OnCommandExecuted(object result, bool async)
        {
        }
    
        [DiagnosticName("Microsoft.EntityFrameworkCore.Database.Command.CommandError")]
        public void OnCommandError(Exception exception, bool async)
        {
        }
    
        private static void AddQueryHintsBasedOnTags(DbCommand command)
        {
            if (command.CommandType != CommandType.Text || !(command is SqlCommand))
            {
                return;
            }
    
            if (command.CommandText.Contains(Constants.SQL_TAG_QUERYHINT_RECOMPILE) && !command.CommandText.Contains("OPTION (RECOMPILE)", StringComparison.InvariantCultureIgnoreCase))
            {
                command.CommandText = command.CommandText + "\nOPTION (RECOMPILE)";
            }
            else if (command.CommandText.Contains(Constants.SQL_TAG_QUERYHINT_OPTIMIZE_UNKNOWN_USER) && !command.CommandText.Contains("OPTION (OPTIMIZE FOR (@__SomeUserParam_0 UNKNOWN))", StringComparison.InvariantCultureIgnoreCase))
            {
                command.CommandText = command.CommandText + "\nOPTION (OPTIMIZE FOR (@__SomeUserParam_0 UNKNOWN))";
            }
        }
    }
    

    편집 :이 컨텍스트 객체에 가입하지 않기 때문에 당신의 DiagnosticSource에 가입 ​​할 경우주의하십시오. DiagnosticSource 다른 수명을 가지고 (많은 상황에 대한 원인이 될 수 있습니다.) 당신이 작성하는 모든 범위의 문맥에 가입한다면, 당신은 결국 더 많은 구독을 만들 것입니다. 해결책은 단 하나의 구독을 만들 여기 여기 내 대답을 참조하십시오.

  6. from https://stackoverflow.com/questions/25145667/ef-6-parameter-sniffing by cc-by-sa and MIT license