We've a stored procedure that happens to build up some dynamic SQL and execute via a parametrised call to sp_executesql
.
Under normal conditions, this works wonderfully, and has made a large benefit in execution times for the procedure (~8 seconds to ~1 second), however, under some unknown conditions, something strange happens, and performance goes completely the other way (~31 seconds), but only when executed via RPC (i.e. a call from a .Net app with the SqlCommand.CommandType
of CommandType.StoredProcedure
; or as a remote query from a linked server) - if executed as a SQL Batch using SQL Server Management Studio, we do not see the degradation in performance.
Altering the white-space in the generated SQL and recompiling the stored procedure, seems to resolve the issue at least in the short term, but we'd like to understand the cause, or ways to force the execution plans to be rebuilt for the generated SQL; but at the moment, I'm not sure how to proceed with either?
To illustrate, the Stored Procedure, looks a little like:
CREATE PROCEDURE [dbo].[usp_MyObject_Search]
@IsActive AS BIT = NULL,
@IsTemplate AS BIT = NULL
AS
DECLARE @WhereClause NVARCHAR(MAX) = ''
IF @IsActive IS NOT NULL
BEGIN
SET @WhereClause += ' AND (svc.IsActive = @xIsActive) '
END
IF @IsTemplate IS NOT NULL
BEGIN
SET @WhereClause += ' AND (svc.IsTemplate = @xIsTemplate) '
END
DECLARE @Sql NVARCHAR(MAX) = '
SELECT svc.[MyObjectId],
svc.[Name],
svc.[IsActive],
svc.[IsTemplate]
FROM dbo.MyObject svc WITH (NOLOCK)
WHERE 1=1 ' + @WhereClause + '
ORDER BY svc.[Name] Asc'
EXEC sp_executesql @Sql, N'@xIsActive BIT, @xIsTemplate BIT',
@xIsActive = @IsActive, @xIsTemplate = @IsTemplate
With this approach, the query plan will be cached for the permutations of NULL/not-NULL, and we're getting the benefit of cached query plans. What I don't understand is why it would use a different query plan when executed remotely vs. locally after "something happens"; I also don't understand what the "something" might be?
I realise I could move away from parametrisation, but then we'd lose the benefit of caching what are normally good execution plans.