SQL Parameter
sniffing
For last few days we were having a problem with a few
stored procedures that stop functioning for some set of values using SQL Server
2012. The stored procedure work correctly with one set of parameters, while with
another set would timeout and fail.
The initial way that we
bypassed this problem was that when this circumstance was found, we would alter
the stored procedure and this seemed to fix it. As you can imagine it wasn't an
ideal situation for an enterprise application because the problem would need to
be found and reported before we would even know it existed but we struggled to
find any proposed solutions to the problem.
After much investigation it
was suggested that this could be caused by parameter sniffing. SQL Server does
this by tracking what happens to the parameters passed into the stored procedure
when creating the stored procedure and working out the execution plan. So the
execution plan generated by SQL server was good most of the time but the plan
would contain situations where the procedure would fail.
Below is a
stored procedure that allows parameter sniffing. This is not one of the
stored procedures that did have the problem, but a stored procedure where
parameter sniffing occurs:
ALTER PROCEDURE [dbo].[ReportJobSeekerDetails]
@MarketPlaceID BIGINT,
@RoleName NVARCHAR(20),
@CountryId SMALLINT = NULL,
@StateId BIGINT = NULL,
@UserStatus TINYINT = NULL,
@RegStartDate DATETIME = NULL,
@RegEndDate DATETIME = NULL,
@StartRowNumber INT,
@PageSize INT,
@SortExpression NVARCHAR(100)
AS
BEGIN
-- SET NOCOUNT ON
added to prevent extra result sets from
SET NOCOUNT OFF;
IF @PageSize >
100
SET @PageSize = 100
SELECT COUNT(*) 'RowCount'
FROM
aspnet_users au
INNER JOIN aspnet_membership am ON
au.userID = am.userid
INNER JOIN aspnet_usersinroles ur ON
ur.userID = au.UserId
INNER JOIN UserPublicProfile upp ON
upp.UserID = ur.ID
LEFT JOIN Country c ON
upp.CountryID = c.Id
LEFT JOIN State s ON
upp.StateID = s.ID
WHERE (@CountryId IS NULL OR c.Id = @CountryId)
AND
(@StateId IS NULL OR upp.StateId = @StateId)
AND
(@UserStatus IS NULL OR am.IsLockedOut = @UserStatus)
AND
(@RegStartDate IS
NULL OR
am.CreateDate > @RegStartDate)
AND
(@RegEndDate IS NULL OR
am.CreateDate < @RegEndDate)
END
With
the following addition of variables it
prevents SQL Server from having the ability to perform parameter
sniffing:
ALTER PROCEDURE [dbo].[ReportJobSeekerDetails]
@MarketPlaceID BIGINT,
@RoleName NVARCHAR(20),
@CountryId SMALLINT = NULL,
@StateId BIGINT = NULL,
@UserStatus TINYINT = NULL,
@RegStartDate DATETIME = NULL,
@RegEndDate DATETIME = NULL,
@StartRowNumber INT,
@PageSize INT,
@SortExpression NVARCHAR(100)= NULL,
@TotalCount INT OUTPUT
AS
BEGIN
DECLARE @CountryIdLocal
SMALLINT = @CountryId
DECLARE @StateIdLocal
BIGINT = @StateId
DECLARE @UserStatusLocal
TINYINT = @UserStatus
DECLARE @RegStartDateLocal DATETIME = @RegStartDate
DECLARE @RegEndDateLocal
DATETIME = @RegEndDate
SELECT @TotalCount = COUNT(au.UserName)
FROM dbo.aspnet_users au
INNER JOIN dbo.aspnet_membership am
ON au.userID = am.userid
INNER JOIN dbo.aspnet_usersinroles ur ON ur.userID = au.UserId
INNER JOIN UserPublicProfile upp ON
upp.UserID = ur.ID
LEFT JOIN Country c ON upp.CountryID = c.Id
LEFT JOIN State s ON upp.StateID = s.ID
WHERE (@CountryIdLocal IS NULL OR c.Id = @CountryIdLocal)
AND
(@StateIdLocal IS
NULL OR upp.StateId = @StateIdLocal)
AND
(@UserStatusLocal IS
NULL OR am.IsLockedOut = @UserStatusLocal)
AND
(@RegStartDateLocal IS NULL OR am.CreateDate >
@RegStartDateLocal)
AND
(@RegEndDateLocal IS
NULL OR
am.CreateDate < @RegEndDateLocal)
END
By implementing the small change demonstrated above on a couple of
troublesome stored procedures, this fixed our problem with our stored procedures
randomly not working with certain parameters. This proved that the problem was
caused by parameter sniffing and left me wondering how many other people are out
there with the same problem.
Happy Coding!!