We are struggling with a strange problem: a stored procedure become extremely slow when raw SQL is executed fairly fast.
We have
Code:
ALTER Procedure [dbo].[spGetMovieShortDataList](
@MediaID int = null,
@Rfa nvarchar(8) = null,
@LicenseWindow nvarchar(8) = null,
@OwnerID uniqueidentifier = null,
@LicenseType nvarchar(max) = null,
@PriceGroupID uniqueidentifier = null,
@Format nvarchar(max) = null,
@GenreID uniqueidentifier = null,
@Title nvarchar(max) = null,
@Actor nvarchar(max) = null,
@ProductionCountryID uniqueidentifier = null,
@DontReturnMoviesWithNoLicense bit = 0,
@DontReturnNotReadyMovies bit = 0,
@take int = 10,
@skip int = 0,
@order nvarchar(max) = null,
@asc bit = 1)
as
begin
declare @SQLString nvarchar(max);
declare @ascending nvarchar(5);
declare @ParmDefinition nvarchar(max);
set @ParmDefinition = '@MediaID int,
declare @now DateTime;
declare @Rfa nvarchar(8),
@LicenseWindow nvarchar(8),
@OwnerID uniqueidentifier,
@LicenseType nvarchar(max),
@PriceGroupID uniqueidentifier,
@Format nvarchar(max),
@GenreID uniqueidentifier,
@Title nvarchar(max),
@Actor nvarchar(max),
@ProductionCountryID uniqueidentifier,
@DontReturnMoviesWithNoLicense bit = 0,
@DontReturnNotReadyMovies bit = 0,
@take int,
@skip int,
@now DateTime';
set @ascending = case when @asc = 1 then 'ASC' else 'DESC' end
set @now = GetDate();
set @SQLString = 'SELECT distinct m.ID, m.EpisodNo, m.MediaID, p.Dubbed, pf.Format, t.OriginalTitle into #temp
FROM Media m
inner join Asset a1 on m.ID=a1.ID
inner join Asset a2 on a1.ParentID=a2.ID
inner join Asset a3 on a2.ParentID=a3.ID
inner join Title t on t.ID = a3.ID
inner join Product p on a2.ID = p.ID
left join AssetReady ar on ar.AssetID = a1.ID
left join License l on l.ProductID=p.ID
left join ProductFormat pf on pf.ID = p.Format '
+ CASE WHEN @PriceGroupID IS NOT NULL THEN
'left join LicenseToPriceGroup lpg on lpg.LicenseID = l.ID ' ELSE '' END
+ CASE WHEN @Title IS NOT NULL THEN
'left join LanguageAsset la on la.AssetID = m.ID ' ELSE '' END
+ CASE WHEN @LicenseType IS NOT NULL THEN
'left join LicenseType lt on lt.ID=l.LicenseTypeID ' ELSE '' END
+ CASE WHEN @Actor IS NOT NULL THEN
'left join Cast c on c.AssetID = a1.ID ' ELSE '' END
+ CASE WHEN @GenreID IS NOT NULL THEN
'left join ListToCountryToAsset lca on lca.AssetID=a1.ID ' ELSE '' END
+ CASE WHEN @ProductionCountryID IS NOT NULL THEN
'left join ProductionCountryToAsset pca on pca.AssetID=t.ID ' ELSE '' END
+
'where (
1 = case
when @Rfa = ''All'' then 1
when @Rfa = ''Ready'' then ar.Rfa
when @Rfa = ''NotReady'' and (l.TbaWindowStart is null OR l.TbaWindowStart = 0) and ar.Rfa = 0 and ar.SkipRfa = 0 then 1
when @Rfa = ''Skipped'' and ar.SkipRfa = 1 then 1
end) '
+
CASE WHEN @LicenseWindow IS NOT NULL THEN
'AND
1 = (case
when (@LicenseWindow = 1 And (l.WindowEnd < @now and l.TbaWindowEnd = 0)) then 1
when (@LicenseWindow = 2 And (l.TbaWindowStart = 0 and l.WindowStart < @now and (l.TbaWindowEnd = 1 or l.WindowEnd > @now))) then 1
when (@LicenseWindow = 4 And ((l.TbaWindowStart = 1 or l.WindowStart > @now) and (l.TbaWindowEnd = 1 or l.WindowEnd > @now))) then 1
when (@LicenseWindow = 3 And ((l.WindowEnd < @now and l.TbaWindowEnd = 0) or (l.TbaWindowStart = 0 and l.WindowStart < @now and (l.TbaWindowEnd = 1 or l.WindowEnd > @now)))) then 1
when (@LicenseWindow = 5 And ((l.WindowEnd < @now and l.TbaWindowEnd = 0) or ((l.TbaWindowStart = 1 or l.WindowStart > @now) and (l.TbaWindowEnd = 1 or l.WindowEnd > @now)))) then 1
when (@LicenseWindow = 6 And ((l.TbaWindowStart = 0 and l.WindowStart < @now and (l.TbaWindowEnd = 1 or l.WindowEnd > @now)) or ((l.TbaWindowStart = 1 or l.WindowStart > @now) and (l.TbaWindowEnd = 1 or l.WindowEnd > @now)))) then 1
when ((@LicenseWindow = 7 Or @LicenseWindow = 0) And ((l.WindowEnd < @now and l.TbaWindowEnd = 0) or (l.TbaWindowStart = 0 and l.WindowStart < @now and (l.TbaWindowEnd = 1 or l.WindowEnd > @now)) or ((l.TbaWindowStart = 1 or l.WindowStart > @now) and (l.TbaWindowEnd = 1 or l.WindowEnd > @now)))) then 1
end) ' ELSE '' END
+ CASE WHEN @OwnerID IS NOT NULL THEN
'AND (l.OwnerID = @OwnerID) ' ELSE '' END
+ CASE WHEN @MediaID IS NOT NULL THEN
'AND (m.MediaID = @MediaID) ' ELSE '' END
+ CASE WHEN @LicenseType IS NOT NULL THEN
'AND (lt.Name = @LicenseType) ' ELSE '' END
+ CASE WHEN @PriceGroupID IS NOT NULL THEN
'AND (lpg.PriceGroupID = @PriceGroupID) ' ELSE '' END
+ CASE WHEN @Format IS NOT NULL THEN
'AND (pf.Format = @Format) ' ELSE '' END
+ CASE WHEN @GenreID IS NOT NULL THEN
'AND (lca.ListID = @GenreID) ' ELSE '' END
+ CASE WHEN @DontReturnMoviesWithNoLicense = 1 THEN
'AND (l.ID is not null) ' ELSE '' END
+ CASE WHEN @Title IS NOT NULL THEN
'AND (t.OriginalTitle like N''%' + @Title + '%'' OR la.LocalTitle like N''%' + @Title + '%'') ' ELSE '' END
+ CASE WHEN @Actor IS NOT NULL THEN
'AND (rtrim(ltrim(replace(c.FirstName + '' '' + c.MiddleName + '' '' + c.LastName, '' '', '' ''))) like ''%'' + rtrim(ltrim(replace(@Actor,'' '','' ''))) + ''%'') ' ELSE '' END
+ CASE WHEN @DontReturnNotReadyMovies = 1 THEN
'AND ((ar.ID is not null) AND (ar.Ready = 1) AND (ar.CountryID = l.CountryID))' ELSE '' END
+ CASE WHEN @ProductionCountryID IS NOT NULL THEN
'AND (pca.ProductionCountryID = @ProductionCountryID)' ELSE '' END
+
'
select #temp.* ,ROW_NUMBER() over (order by ';
if @order = 'Title'
begin
set @SQLString = @SQLString + 'OriginalTitle';
end
else if @order = 'MediaID'
begin
set @SQLString = @SQLString + 'MediaID';
end
else
begin
set @SQLString = @SQLString + 'ID';
end
set @SQLString = @SQLString + ' ' + @ascending + '
) rn
into #numbered
from #temp
declare @count int;
select @count = MAX(#numbered.rn) from #numbered
while (@skip >= @count )
begin
set @skip = @skip - @take;
end
select ID, MediaID, EpisodNo, Dubbed, Format, OriginalTitle, @count TotalCount from #numbered
where rn between @skip and @skip + @take
drop table #temp
drop table #numbered';
execute sp_executesql @SQLString,@ParmDefinition, @MediaID, @Rfa, @LicenseWindow, @OwnerID, @LicenseType, @PriceGroupID, @Format, @GenreID,
@Title, @Actor, @ProductionCountryID, @DontReturnMoviesWithNoLicense,@DontReturnNotReadyMovies, @take, @skip, @now
end
The stored procedure was working pretty good and fast (it's execution usually took 1-2 seconds).
Example of call
DBCC FREEPROCCACHE
EXEC value = [dbo].[spGetMovieShortDataList]
@LicenseWindow =N'1',
@Rfa = N'NotReady',
@DontReturnMoviesWithNoLicense = False,
@DontReturnNotReadyMovies = True,
@take = 20,
@skip = 0,
@asc = False,
@order = N'ID'
Basically during execution of the stored procedure the executed 3 SQL queries, the first Select Into
query takes 99% of time.
This query is
declare @now DateTime;
set @now = GetDate();
SELECT DISTINCT
m.ID, m.EpisodNo, m.MediaID, p.Dubbed, pf.Format, t.OriginalTitle
FROM Media m
INNER JOIN Asset a1 ON m.ID = a1.ID
INNER JOIN Asset a2 ON a1.ParentID = a2.ID
INNER JOIN Asset a3 ON a2.ParentID = a3.ID
INNER JOIN Title t ON t.ID = a3.ID
INNER JOIN Product p ON a2.ID = p.ID
LEFT JOIN AssetReady ar ON ar.AssetID = a1.ID
LEFT JOIN License l on l.ProductID = p.ID
LEFT JOIN ProductFormat pf on pf.ID = p.Format
WHERE
((l.TbaWindowStart is null OR l.TbaWindowStart = 0)
and ar.Rfa = 0 and ar.SkipRfa = 0)
And (l.WindowEnd < @now and l.TbaWindowEnd = 0 )
AND ((ar.ID is not null) AND (ar.Ready = 1) AND (ar.CountryID = l.CountryID))
This stored procedure, after massive data update on the database (a lot tables and rows were affected by the update, however DB size was almost unchanged, now it is 752 ) become to work extremely slow. Now it takes from 20 to 90 seconds.
If I take raw SQL query from the stored procedure - it is executed within 1-2 seconds.
We've tried:
the stored procedure is created with parameters
SET ANSI_NULLS ON SET QUOTED_IDENTIFIER ON
recreate the stored procedure with parameter with recompile
DBCC FREEPROCCACHE
UPDATE STATISTICS Media WITH FULLSCAN
However the execution of the stored procedure is still >> 30 seconds.
But if I run the SQL query which is generated by the SP - it is executed for less than 2 seconds.
I've compared execution plans for SP and for the raw SQL - they are quite different. During execution of RAW SQL - the optimizer is using Merge Joins, but when we execute SP - it uses Hash Match (Inner Join), like there are no indexes.
If someone knows what could it be - please help. Thanks in advance!
Storage of Execution Plan – One of the biggest reasons why you are facing slow procedures in SQL Server is probably because your Execution plan is stored in the cache. To find out if it is in the cache, you need to search it there and see if it exists in the top 10 appearing plans.
Because of this, it's probably more likely that your stored procedure plans are being ran from cached plans while your individually submitted query texts may not be utilizing the cache. Because of this, the stored procedure may in fact be executing faster because it was able to reuse a cached plan.
SQL Server uses nested loop, hash, and merge joins. If a slow-performing query is using one join technique over another, you can try forcing a different join type. For example, if a query is using a hash join, you can force a nested loops join by using the LOOP join hint.
Try using using the hint OPTIMIZE FOR UNKNOWN
. If it works, this may be better than forcing a recompile every time. The problem is that, the most efficient query plan depends on the actual value of the date paramter being supplied. When compiling the SP, sql server has to make a guess on what actual values will be supplied, and it is likely making the wrong guess here. OPTIMIZE FOR UNKNOWN
is meant for this exact problem.
At the end of your query, add
OPTION (OPTIMIZE FOR (@now UNKNOWN))
http://blogs.msdn.com/b/sqlprogrammability/archive/2008/11/26/optimize-for-unknown-a-little-known-sql-server-2008-feature.aspx
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With