1

I am using sp_executesql to pass a complicated selection with a few parameters. It is much slower doing it this way than it is by taking it out of the stored procedure and declaring the variables.

I have seen many questions about SQL parameter sniffing and my scenario sounds like this could be the case. However even after calling DBCC FREEPROCCACHE or amending the outer Select with Option (Recompile) it still uses a different and inefficent execution plan compared to writing the same query outside the stored procedure.

However still using the stored procedure but setting up copies of the parameters as local variables will use the efficent execution plan.

Does this scenario rule out SQL parameter sniffing as the cause? Becuase i recompile the query surely there is no pre existing execution plan it uses. If so what could be possible other reasons for this behaviour?

Just to give you an idea of the sql query you can see it below (Messy as generated via Entity framework). This is the fast query but when put into the sp_executesql proc with the variable taken out and put in parameters it generates the inefficient execution plan

 DECLARE    @p__linq__0 INT = 2032
,@p__linq__1 UNIQUEIDENTIFIER = '8CC161FC-B8DE-4746-BA4F-62FA55DF26DE'
,@p__linq__2 uniqueidentifier= '8CC161FC-B8DE-4746-BA4F-62FA55DF26DE',
@p__linq__3 uniqueidentifier= '8CC161FC-B8DE-4746-BA4F-62FA55DF26DE',
@p__linq__4 uniqueidentifier= '8CC161FC-B8DE-4746-BA4F-62FA55DF26DE',
@p__linq__5 INT = 6771

SELECT 
[Limit1].[UserIdValue] AS [UserIdValue]
FROM   (SELECT [Extent1].[Id] AS [Id]
    FROM [dbo].[Request] AS [Extent1]
    WHERE ([Extent1].[InstanceId] = @p__linq__0) AND ([Extent1].[DeletedDate] IS NULL) AND (( EXISTS (SELECT 
        1 AS [C1]
        FROM ( SELECT 
            [Extent2].[TeamId] AS [TeamId], 
            [Extent2].[HasUpdateAccess] AS [HasUpdateAccess]
            FROM [dbo].[RequestTypeTeam] AS [Extent2]
            WHERE [Extent1].[RequestTypeId] = [Extent2].[RequestTypeId]
        )  AS [Project1]
        WHERE ([Project1].[HasUpdateAccess] = 1) AND ( EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[UserTeam] AS [Extent3]
            WHERE ([Project1].[TeamId] = [Extent3].[TeamId]) AND ([Extent3].[UserId] = @p__linq__1)
        ))
    )) OR (([Extent1].[InsertUserId] = @p__linq__2) AND ( EXISTS (SELECT 
        1 AS [C1]
        FROM ( SELECT 
            [Extent4].[TeamId] AS [TeamId], 
            [Extent4].[HasCreatorAccess] AS [HasCreatorAccess]
            FROM [dbo].[RequestTypeTeam] AS [Extent4]
            WHERE [Extent1].[RequestTypeId] = [Extent4].[RequestTypeId]
        )  AS [Project4]
        WHERE ([Project4].[HasCreatorAccess] = 1) AND ( EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[UserTeam] AS [Extent5]
            WHERE ([Project4].[TeamId] = [Extent5].[TeamId]) AND ([Extent5].[UserId] = @p__linq__3)
        ))
    ))) OR ( EXISTS (SELECT 
        1 AS [C1]
        FROM [dbo].[RequestTeam] AS [Extent6]
        WHERE ([Extent1].[Id] = [Extent6].[RequestId]) AND ([Extent6].[TeamId] IN (3147, 3165))
    )) OR ( EXISTS (SELECT 
        1 AS [C1]
        FROM ( SELECT 
            (SELECT 
                COUNT(1) AS [A1]
                FROM [dbo].[RequestControlData] AS [Extent8]
                WHERE ([Project8].[Id] = [Extent8].[ControlId]) AND ([Extent8].[RequestId] = [Extent1].[Id]) AND ([Extent8].[UserIdValue] = @p__linq__4)) AS [C1]
            FROM ( SELECT 
                [Extent7].[Id] AS [Id]
                FROM [dbo].[Control] AS [Extent7]
                WHERE ([Extent7].[RequestTypeId] IS NOT NULL) AND ([Extent1].[RequestTypeId] = [Extent7].[RequestTypeId]) AND ([Extent7].[DeletedDate] IS NULL) AND ([Extent7].[IsAuthorisation] = 1)
            )  AS [Project8]
        )  AS [Project9]
        WHERE [Project9].[C1] > 0
    ))) AND ( NOT ([Extent1].[StatusId] IN (1071))) AND ( NOT ([Extent1].[RequestTypeId] IN (1215)))) AS [Filter11] 
OUTER APPLY  (SELECT TOP (1) 
    [Extent9].[ControlId] AS [ControlId], 
    [Extent9].[UserIdValue] AS [UserIdValue], 
    [Extent10].[Id] AS [Id], 
    [Extent10].[SharedControlId] AS [SharedControlId]
    FROM  [dbo].[RequestControlData] AS [Extent9]
    INNER JOIN [dbo].[Control] AS [Extent10] ON [Extent9].[ControlId] = [Extent10].[Id]
    WHERE ([Filter11].[Id] = [Extent9].[RequestId]) AND (([Extent10].[SharedControlId] = @p__linq__5) OR (([Extent10].[SharedControlId] IS NULL) AND (@p__linq__5 IS NULL
    ))) 
    )
     AS [Limit1]  
user2945722
  • 1,293
  • 1
  • 16
  • 35
  • I don't see how using local variables would help if recompile doesn't. Did you try `EXECUTE dbo. WITH RECOMPILE;`? If it performs better, then parameter sniffing is the issue and you could consider `OPTIMIZE FOR (UNKNOWN)` query hints in your procedure. – jumxozizi Sep 22 '16 at 11:06
  • I did try `WITH RECOMPILE` and it generated the slow execution plan. Just trired `OPTIMIZIE FOR (@myParam UNKNOWN)` and it genereated the fast plan. Interestingly tho using Optimize for and passing the real values instead of unknown generated the slow plan. Still dont understand why I am experiencing this behaviour tho. – user2945722 Sep 22 '16 at 11:16
  • You could try to compare the execution plans to figure out what happens. You can also check the parameter values the procedure has been compiled with. – jumxozizi Sep 22 '16 at 11:28
  • have you updated statistics? that's the first step. – Bruce Dunwiddie Sep 23 '16 at 14:17
  • also, it almost appears that you're counting sp_executesql as the stored procedure with "parameter sniffing"? did you actually create a new stored procedure, or are you just running raw sql using sp_executesql? – Bruce Dunwiddie Sep 23 '16 at 14:20

2 Answers2

0

I think your problem is with the design rather than the data, multiple sub-queries are more expensive especially with the OR operator

I would rather use multiple table variables and left outer joins

Marinus
  • 157
  • 6
0

Ran into the same issue again and wanted to post the answer that worked for me.

I ran UPDATE STATISTICS [TableName] WITH FULLSCAN on the various tables relevant to my query, after this it worked as fast as it did using local variables.

Intresting to note that without the WITH FULLSCAN it would still be slow. Also after updating the stats it was fast the first run and then slow the next runs so I had to keep updating the stats to get it to run fast, I then tried updating the stats, running the query (was a fast run) and then calling DBCC FREEPROCCACHEand this seemed to fix it so it was fast every time I ran it

I guess this means it wasn't parameter sniffing that was the issue.

user2945722
  • 1,293
  • 1
  • 16
  • 35