I have a stored procedure that takes about 15 seconds to execute. When hundreds of requests come in, we are seeing unacceptable page load times. Sometimes two minutes. The page loads results based on an ID, so it's not the same results for everyone.
My solution was to use a staging table and only update it if the page hasn't loaded for 5 minutes. I thought this would reduce load on that stored procedure. But now I'm seeing problems with this staging table idea.
If page hasn't been hit for 5 minutes, then DELETE from the staging table based on the ID, then run the stored procedure and INSERT the results into the staging table.
Then UPDATE the staging table with a "total result count", SELECT from the staging table based on ID
As you can see it's doing a DELETE, INSERT, UPDATE, and SELECT. Under load I'm getting tons of deadlocks.
A few questions:
- what is best practice for returning expensive result sets to simply display on a web page?
- how can the staging table approach be improved and assure no deadlocks?
Code:
CREATE procedure [dbo].[BB02_ListFundraisersForEvent] (
@DesignEventId int,
@Offset int,
@PageSize int,
@SearchTerms varchar(100) = null,
@OrderByField varchar(25) = 'DEFAULT',
@OrderByDirection varchar(5) = 'ASC'
)
-- exec BB02_ListFundraisersForEvent 38639, 0, 10, '', '', 'ASC', null
as
set transaction isolation level read uncommitted
declare @UpdateIncrement DateTime = DATEADD(MINUTE, -5, GETDATE());
declare @FundraiserCount int;
declare @LastUpdated DateTime;
declare @PAGE_STATUS_CANCELED int;
declare @TOTAL_TYPE_NON_REJECTED int;
declare @TOTAL_TYPE_REGISTRATION int;
declare @PROFILE_APPEAL_WEB_DIR_FAMILY int;
declare @PROFILE_LEVEL_WEB_DIR_FAMILY int;
set @TOTAL_TYPE_NON_REJECTED = 2;
set @TOTAL_TYPE_REGISTRATION = 3;
set @PAGE_STATUS_CANCELED = 3
set @PROFILE_APPEAL_WEB_DIR_FAMILY = 3;
set @PROFILE_LEVEL_WEB_DIR_FAMILY = 2;
if @OrderByField not in ('FirstName', 'LastName', 'TotalRaised') set @OrderByField = 'DEFAULT';
IF isnull(@SearchTerms, '') = ''
BEGIN
select @FundraiserCount = (select count(*) from bb02_olr_getsupporterscache where designeventid = @DesignEventId)
select @LastUpdated = (select top 1 lastupdated from bb02_olr_getsupporterscache where designeventid = @DesignEventId)
IF( (@FundraiserCount = 0) OR (@LastUpdated < @UpdateIncrement ) OR (ISNULL(@LastUpdated, '') = '') )
BEGIN
DELETE FROM BB02_OLR_GetSupportersCache
WHERE designeventid = @DesignEventId
INSERT INTO bb02_olr_getsupporterscache (DesignEventId,
AppealName,
AppealWebDirectory,
FirstName,
ImageChoice,
LastName,
PhotoURL,
ProfileWebDirectory,
TotalRaisedOffline,
TotalRaisedOnline,
TotalContributions,
DisplayPhoto,
HasStockImages,
LastUpdated)
SELECT
DesignEventId,
AppealName,
AppealWebDirectory,
FirstName,
ImageChoice,
LastName,
PhotoURL,
ProfileWebDirectory,
TotalRaisedOffline,
TotalRaisedOnline,
TotalContributions,
DisplayPhoto,
HasStockImages,
getdate() as LastUpdated
FROM (
-- fundraising pages
SELECT
de.DesignEventId,
egg.EventGivingGroupName as AppealName,
awd.WebDirectoryName as AppealWebDirectory,
c.FirstName,
egg.ImageChoice,
c.LastName,
egg.PhotoUrl,
cwd.WebDirectoryName as ProfileWebDirectory,
eggt.TotalRaisedOffline,
eggt.TotalRaisedOnline,
eggt.TotalContributions,
CAST(egg.DisplayPhoto AS bit) AS DisplayPhoto,
CAST(CASE WHEN ISNULL(dei.DesignEventId, 0) != 0 then 1 else 0 end as bit) as HasStockImages
FROM
BB02_Event e
INNER JOIN
BB02_EventFundraiserRevenueStream efrs on e.EventId = efrs.EventId
INNER JOIN
BB02_EventGivingGroup egg on efrs.EventFundraiserRevenueStreamId = egg.EventFundraiserRevenueStreamId
INNER JOIN
BB02_EventGivingGroupTotal eggt on egg.EventGivingGroupId = eggt.EventGivingGroupId
INNER JOIN
BB02_Consumer c on c.ConsumerId = egg.ConsumerId
INNER JOIN
BB02_WebDirectory cwd on cwd.WebDirectoryId = c.DefaultWebDirectoryId and cwd.WebDirectoryFamilyId = @PROFILE_LEVEL_WEB_DIR_FAMILY
INNER JOIN
BB02_WebDirectory awd on awd.EventGivingGroupId = egg.EventGivingGroupId and awd.WebDirectoryFamilyId = @PROFILE_APPEAL_WEB_DIR_FAMILY
inner join BB02_DesignEvent de on e.EventId = de.EventId and egg.DesignId = de.DesignId
left join (select distinct DesignEventId from BB02_DesignEventImage) dei on de.DesignEventId = dei.DesignEventId
where eggt.EventGivingGroupTotalTypeId =
case when de.AddFeesToTotal = 1 then @TOTAL_TYPE_REGISTRATION -- 3 includes registration fees
else @TOTAL_TYPE_NON_REJECTED /* 1 = Confirmed, 2 = Not Rejected */
end
and egg.Status <> @PAGE_STATUS_CANCELED
and de.DesignEventId = @DesignEventId
and egg.IsDeleted = 0
union all
-- registrants without pages
select
cer.DesignEventId,
'' as AppealName,
'' as AppealWebDirectory,
FirstName,
'' as ImageChoice,
LastName,
'' as PhotoURL,
'' as ProfileWebDirectory,
0 as TotalRaisedOffline,
0 as TotalRaisedOnline,
0 as TotalContributions,
'' as DisplayPhoto,
cast(case when isnull(dei.DesignEventId, 0) != 0 then 1 else 0 end as bit) as HasStockImages
from BB02_ConsumerEventRegistration cer
left join (select distinct DesignEventId from BB02_DesignEventImage) dei on cer.DesignEventId = dei.DesignEventId
where cer.DesignEventId = @DesignEventId
and cer.ConsumerId not in (
select egg.ConsumerId
from BB02_EventGivingGroup egg
inner join BB02_EventFundraiserRevenueStream efrs on efrs.EventFundraiserRevenueStreamId = egg.EventFundraiserRevenueStreamId
inner join BB02_DesignEvent de on efrs.EventId = de.EventId and egg.DesignId = de.DesignId
where de.DesignEventId = @DesignEventId
)
) a
UPDATE bb02_olr_getsupporterscache
SET TotalCount = (select count(*) from bb02_olr_getsupporterscache where DesignEventId = @DesignEventId)
WHERE DesignEventId = @DesignEventId
END
SELECT * FROM (
select
TotalCount,
SupporterId,
AppealName,
AppealWebDirectory,
FirstName,
ImageChoice,
LastName,
PhotoURL,
ProfileWebDirectory,
TotalRaisedOffline,
TotalRaisedOnline,
TotalContributions,
DisplayPhoto,
HasStockImages,
row_number() over (order by
case when @OrderByField = 'FirstName' and @OrderByDirection = 'ASC' then FirstName end asc,
case when @OrderByField = 'FirstName' and @OrderByDirection = 'DESC' then FirstName end desc,
case when @OrderByField = 'LastName' and @OrderByDirection = 'ASC' then LastName end asc,
case when @OrderByField = 'LastName' and @OrderByDirection = 'DESC' then LastName end desc,
case when @OrderByField = 'TotalRaised' and @OrderByDirection = 'ASC' then (TotalRaisedOnline + TotalRaisedOffline) end asc,
case when @OrderByField = 'TotalRaised' and @OrderByDirection = 'DESC' then (TotalRaisedOnline + TotalRaisedOffline) end desc,
case when @OrderByField = 'DEFAULT' and @OrderByDirection = 'ASC' then AppealName end asc,
case when @OrderByField = 'DEFAULT' and @OrderByDirection = 'DESC' then AppealName end desc
) as rownumber
from (
select * from bb02_olr_getsupporterscache where designeventid = @DesignEventId
) a
) q
where q.rownumber > @Offset
and q.rownumber <= @Offset + @PageSize
order by rownumber;
END
IF isnull(@SearchTerms, '') != ''
BEGIN
declare getSearchStringSegments cursor for
select * from dbo.Split(' ', @SearchTerms)
declare
@segmentId int,
@segment varchar(100),
@segment1 varchar(100),
@segment2 varchar(100),
@segment3 varchar(100)
open getSearchStringSegments
fetch next from getSearchStringSegments into @segmentId, @segment
while @@fetch_status = 0 and @segmentId <= 3
begin
print 1;
if @segmentId = 1 set @segment1 = @segment;
if @segmentId = 2 set @segment2 = @segment;
if @segmentId = 3 set @segment3 = @segment;
fetch next from getSearchStringSegments into @segmentId, @segment
end
close getSearchStringSegments;
deallocate getSearchStringSegments;
select @FundraiserCount = (
-- fundraising pages
select count(egg.EventGivingGroupId)
from BB02_Event e
inner join BB02_EventFundraiserRevenueStream efrs on e.EventId = efrs.EventId
inner join BB02_EventGivingGroup egg on efrs.EventFundraiserRevenueStreamId = egg.EventFundraiserRevenueStreamId
inner join BB02_EventGivingGroupTotal eggt on egg.EventGivingGroupId = eggt.EventGivingGroupId
inner join BB02_Consumer c on c.ConsumerId = egg.ConsumerId
inner join BB02_WebDirectory cwd on cwd.WebDirectoryId = c.DefaultWebDirectoryId and cwd.WebDirectoryFamilyId = @PROFILE_LEVEL_WEB_DIR_FAMILY
inner join BB02_WebDirectory awd on awd.EventGivingGroupId = egg.EventGivingGroupId and awd.WebDirectoryFamilyId = @PROFILE_APPEAL_WEB_DIR_FAMILY
inner join BB02_DesignEvent de on e.EventId = de.EventId and egg.DesignId = de.DesignId
where eggt.EventGivingGroupTotalTypeId =
case when de.AddFeesToTotal = 1 then @TOTAL_TYPE_REGISTRATION -- 3 includes registration fees
else @TOTAL_TYPE_NON_REJECTED /* 1 = Confirmed, 2 = Not Rejected */
end
and egg.Status <> @PAGE_STATUS_CANCELED
and de.DesignEventId = @DesignEventId
and egg.IsDeleted = 0
and (
(egg.EventGivingGroupName like '%'+@segment1+'%' or egg.EventGivingGroupName like '%'+@segment2+'%' or egg.EventGivingGroupName like '%'+@segment3+'%')
or (egg.Attribution like '%'+@segment1+'%' or egg.Attribution like '%'+@segment2+'%' or egg.Attribution like '%'+@segment3+'%')
or (c.FirstName like '%'+@segment1+'%' or c.FirstName like '%'+@segment2+'%' or c.FirstName like '%'+@segment3+'%')
or (c.LastName like '%'+@segment1+'%' or c.LastName like '%'+@segment2+'%' or c.LastName like '%'+@segment3+'%')
)
) + (
-- registrants without pages
select count(cer.ConsumerEventRegistrationId)
from BB02_ConsumerEventRegistration cer
where cer.DesignEventId = @DesignEventId
and cer.ConsumerId not in (
select egg.ConsumerId
from BB02_EventGivingGroup egg
inner join BB02_EventFundraiserRevenueStream efrs on efrs.EventFundraiserRevenueStreamId = egg.EventFundraiserRevenueStreamId
inner join BB02_DesignEvent de on efrs.EventId = de.EventId and egg.DesignId = de.DesignId
where de.DesignEventId = @DesignEventId
)
and (
(cer.FirstName like '%'+@segment1+'%' or cer.FirstName like '%'+@segment2+'%' or cer.FirstName like '%'+@segment3+'%')
or (cer.LastName like '%'+@segment1+'%' or cer.LastName like '%'+@segment2+'%' or cer.LastName like '%'+@segment3+'%')
)
and cer.IsDeleted <> 1
)
select * from (
select
@FundraiserCount as TotalCount,
0 as SupporterId,
AppealName,
AppealWebDirectory,
FirstName,
ImageChoice,
LastName,
PhotoURL,
ProfileWebDirectory,
TotalRaisedOffline,
TotalRaisedOnline,
TotalContributions,
DisplayPhoto,
HasStockImages,
row_number() over (order by
case when @OrderByField = 'FirstName' and @OrderByDirection = 'ASC' then FirstName end asc,
case when @OrderByField = 'FirstName' and @OrderByDirection = 'DESC' then FirstName end desc,
case when @OrderByField = 'LastName' and @OrderByDirection = 'ASC' then LastName end asc,
case when @OrderByField = 'LastName' and @OrderByDirection = 'DESC' then LastName end desc,
case when @OrderByField = 'TotalRaised' and @OrderByDirection = 'ASC' then (TotalRaisedOnline + TotalRaisedOffline) end asc,
case when @OrderByField = 'TotalRaised' and @OrderByDirection = 'DESC' then (TotalRaisedOnline + TotalRaisedOffline) end desc,
case when @OrderByField = 'DEFAULT' and @OrderByDirection = 'ASC' then AppealName end asc,
case when @OrderByField = 'DEFAULT' and @OrderByDirection = 'DESC' then AppealName end desc
) as rownumber
from (
-- fundraising pages
select
egg.EventGivingGroupName as AppealName,
awd.WebDirectoryName as AppealWebDirectory,
c.FirstName,
egg.ImageChoice,
c.LastName,
egg.PhotoUrl,
cwd.WebDirectoryName as ProfileWebDirectory,
eggt.TotalRaisedOffline,
eggt.TotalRaisedOnline,
eggt.TotalContributions,
cast(egg.DisplayPhoto as bit) as DisplayPhoto,
cast(case when isnull(dei.DesignEventId, 0) != 0 then 1 else 0 end as bit) as HasStockImages,
row_number() over (order by
case when @OrderByField = 'FirstName' and @OrderByDirection = 'ASC' then c.FirstName end asc,
case when @OrderByField = 'FirstName' and @OrderByDirection = 'DESC' then c.FirstName end desc,
case when @OrderByField = 'LastName' and @OrderByDirection = 'ASC' then c.LastName end asc,
case when @OrderByField = 'LastName' and @OrderByDirection = 'DESC' then c.LastName end desc,
case when @OrderByField = 'TotalRaised' and @OrderByDirection = 'ASC' then (eggt.TotalRaisedOnline + eggt.TotalRaisedOffline) end asc,
case when @OrderByField = 'TotalRaised' and @OrderByDirection = 'DESC' then (eggt.TotalRaisedOnline + eggt.TotalRaisedOffline) end desc,
case when @OrderByField = 'DEFAULT' and @OrderByDirection = 'ASC' then egg.EventGivingGroupName end asc,
case when @OrderByField = 'DEFAULT' and @OrderByDirection = 'DESC' then egg.EventGivingGroupName end desc
) as rownumber
from BB02_Event e
inner join BB02_EventFundraiserRevenueStream efrs on e.EventId = efrs.EventId
inner join BB02_EventGivingGroup egg on efrs.EventFundraiserRevenueStreamId = egg.EventFundraiserRevenueStreamId
inner join BB02_EventGivingGroupTotal eggt on egg.EventGivingGroupId = eggt.EventGivingGroupId
inner join BB02_Consumer c on c.ConsumerId = egg.ConsumerId
inner join BB02_WebDirectory cwd on cwd.WebDirectoryId = c.DefaultWebDirectoryId and cwd.WebDirectoryFamilyId = @PROFILE_LEVEL_WEB_DIR_FAMILY
inner join BB02_WebDirectory awd on awd.EventGivingGroupId = egg.EventGivingGroupId and awd.WebDirectoryFamilyId = @PROFILE_APPEAL_WEB_DIR_FAMILY
inner join BB02_DesignEvent de on e.EventId = de.EventId and egg.DesignId = de.DesignId
left join (select distinct DesignEventId from BB02_DesignEventImage) dei on de.DesignEventId = dei.DesignEventId
where eggt.EventGivingGroupTotalTypeId =
case when de.AddFeesToTotal = 1 then @TOTAL_TYPE_REGISTRATION -- 3 includes registration fees
else @TOTAL_TYPE_NON_REJECTED /* 1 = Confirmed, 2 = Not Rejected */
end
and egg.Status <> @PAGE_STATUS_CANCELED
and de.DesignEventId = @DesignEventId
and egg.IsDeleted = 0
and (
(egg.EventGivingGroupName like '%'+@segment1+'%' or egg.EventGivingGroupName like '%'+@segment2+'%' or egg.EventGivingGroupName like '%'+@segment3+'%')
or (egg.Attribution like '%'+@segment1+'%' or egg.Attribution like '%'+@segment2+'%' or egg.Attribution like '%'+@segment3+'%')
or (c.FirstName like '%'+@segment1+'%' or c.FirstName like '%'+@segment2+'%' or c.FirstName like '%'+@segment3+'%')
or (c.LastName like '%'+@segment1+'%' or c.LastName like '%'+@segment2+'%' or c.LastName like '%'+@segment3+'%')
)
union all
-- registrants without pages
select
'' as AppealName,
'' as AppealWebDirectory,
FirstName,
'' as ImageChoice,
LastName,
'' as PhotoURL,
'' as ProfileWebDirectory,
0 as TotalRaisedOffline,
0 as TotalRaisedOnline,
0 as TotalContributions,
'' as DisplayPhoto,
cast(case when isnull(dei.DesignEventId, 0) != 0 then 1 else 0 end as bit) as HasStockImages,
row_number() over (order by
case when @OrderByField = 'FirstName' and @OrderByDirection = 'ASC' then cer.FirstName end asc,
case when @OrderByField = 'FirstName' and @OrderByDirection = 'DESC' then cer.FirstName end desc,
case when @OrderByField = 'LastName' and @OrderByDirection = 'ASC' then cer.LastName end asc,
case when @OrderByField = 'LastName' and @OrderByDirection = 'DESC' then cer.LastName end desc,
case when @OrderByField = 'TotalRaised' and @OrderByDirection = 'ASC' then (0) end asc,
case when @OrderByField = 'TotalRaised' and @OrderByDirection = 'DESC' then (0) end desc,
case when @OrderByField = 'DEFAULT' and @OrderByDirection = 'ASC' then '' end asc,
case when @OrderByField = 'DEFAULT' and @OrderByDirection = 'DESC' then '' end desc
) as rownumber
from BB02_ConsumerEventRegistration cer
left join (select distinct DesignEventId from BB02_DesignEventImage) dei on cer.DesignEventId = dei.DesignEventId
where cer.DesignEventId = @DesignEventId
and cer.ConsumerId not in (
select egg.ConsumerId
from BB02_EventGivingGroup egg
inner join BB02_EventFundraiserRevenueStream efrs on efrs.EventFundraiserRevenueStreamId = egg.EventFundraiserRevenueStreamId
inner join BB02_DesignEvent de on efrs.EventId = de.EventId and egg.DesignId = de.DesignId
where de.DesignEventId = @DesignEventId
)
and (
(cer.FirstName like '%'+@segment1+'%' or cer.FirstName like '%'+@segment2+'%' or cer.FirstName like '%'+@segment3+'%')
or (cer.LastName like '%'+@segment1+'%' or cer.LastName like '%'+@segment2+'%' or cer.LastName like '%'+@segment3+'%')
)
) a
) q
where q.rownumber > @Offset
and q.rownumber <= @Offset + @PageSize
order by rownumber;
END