SQL死锁 - 流量高

时间:2016-02-10 18:13:11

标签: sql sql-server stored-procedures query-optimization

我有一个存储过程,大约需要15秒才能执行。当数百个请求进入时,我们看到了不可接受的页面加载时间。有时两分钟。该页面根据ID加载结果,因此每个人的结果都不一样。

我的解决方案是使用临时表,只有在页面没有加载5分钟时才更新它。我认为这会减少该存储过程的负载。但现在我发现了这个临时表的问题。

如果页面没有被点击5分钟,则根据ID从登台表中删除,然后运行存储过程并将结果插入登台表。

然后使用"总结果计数"更新登台表,根据ID从登台表中选择

您可以看到它正在执行DELETE,INSERT,UPDATE和SELECT。在负载下,我遇到了大量的死锁。

几个问题:

  1. 返回昂贵的结果集以简单地显示在网页上的最佳做法是什么?
  2. 如何改进临时表方法并确保没有死锁?
  3. 代码:

    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
    

1 个答案:

答案 0 :(得分:0)

根据获取实际数据所需的时间,缓存数据的想法可能确实值得;问题是在网络服务器层而不是在数据库层中,这样做会有多远。

那就是说,既然你已经掌握了这一部分,那就让我们看一下存储过程。

首先是一些一般性评论:

  • 我真的,真的不喜欢READ UNCOMMITTED。我知道你添加了这个以避免锁定,但脏读的想法确实让我感到紧张。您可能会像这样向调用者返回减半或错误的信息。就个人而言,我宁愿等待更长时间才能获得有关信息的信息,而不是获取不良信息。
  • 不要使用SELECT COUNT(*) ...来确定是否有任何记录。服务器需要扫描所有相关记录才能获得数字,但您真正感兴趣的是它是否存在。请使用WHERE EXISTS()
  • 更好的是,既然你事后做了SELECT TOP 1,那就用它的结果吧。如果找到了值,那么必须有记录!
  • 使用ISNULL(@LastUpdated, '') = ''字段/变量时,
  • datetime有点可疑。而是使用@LastUpdated IS NULL进行此项检查
  • 之后您执行DELETE后跟可能是 INSERT的内容。我想这需要一段时间,这就是死锁的来源。正确的锁定应该有帮助,但我不知道它如何与上面的READ UNCOMMITTED合作。
  • INSERT之后,您再次查看新插入的数据,以确定有多少行。由于这里唯一相关的行是我们刚插入的那些,我们可以安全地假设@@ ROWCOUNT已经得到了答案。读取该系统变量不需要花费精力。对SELECT COUNT(*) ...进行操作会对系统造成更大的负担
  • 我个人更喜欢将INSERT分成两部分,并使用临时表来保存中间结果。我不确定它会在性能方面做出多大的改变(理论上它对服务器来说更多的工作)但我认为它会减少惊喜,特别是如果你把一些统计数据更新。只有测试才能真正说明。
  • 拥有临时表的副作用是您只需返回表中的结果,而不需要返回缓存表,避免与其他进程交互。
  • 我还建议使用动态sql,因为它提供了排序的灵活性。 `RowNumber()&#39;里面的套管结构。肯定有效,但我怀疑它效率过高。

无论如何,至于锁定。您获得死锁的原因是您在同一记录上进行了大量操作,并且可能同时从不同的连接进行。因此,一个连接可能正在执行INSERT而另一个连接尝试执行DELETEUPDATE等等。这可能不会很好。 为了避免这种情况,我会选择锁定,但即使在那里我也可能会遇到问题,因此我会尝试使用“暂停”的应用程序锁定。任何连接,而另一个连接仍在刷新缓存表中的数据。更重要的是,我们可以限制&#39;这会锁定到正在处理的@EventId

快点做一些&amp;我在下面提出的代码的脏更改。可能会出现一些语法错误,甚至可能出现一些逻辑错误,因此您可能需要根据需要进行调整/修复。但我希望它会指出你正确的方向。

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;
    declare @TotalCount int
    declare @cache_was_updated bit
    declare @sql nvarchar(max)
    declare @rc int
    declare @LockName sysname

    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 @OrderByDirection not in ('ASC', 'DESC') set @OrderByDirection = 'ASC';

    SET @cache_was_updated = 0

    IF isnull(@SearchTerms, '') = ''
        BEGIN 

            select @LastUpdated = NULL
            select TOP 1 @LastUpdated = lastupdated from bb02_olr_getsupporterscache where designeventid = @DesignEventId


            IF( (@LastUpdated IS NULL) -- no value found means no data present for given @DesignEventId
                 OR (@LastUpdated < @UpdateIncrement ) ) -- or value found, but too far in the past)
                BEGIN
                    -- prepare new batch
                    set @LockName = 'CacheUpdate' + Convert(nvarchar(100), @DesignEventId)


                    -- get exclusive applock on this @DesignEventId
                    -- => we should be the only ones that can update this!

                    EXEC @rc = sp_getapplock @Resource = @LockName,
                                             @LockMode = 'Exlusive',
                                             @LockTimeout = 0, -- if another process already has the lock, we will skip the update
                                             @lockOwner = 'Session'

                    IF @rc > 0
                        BEGIN
                            -- lock obtained
                            -- => let's do the cache update

                            SET @TotalCount = 0

                            -- 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
                                INTO #staging
                                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


                                SET @TotalCount = @TotalCount + @@ROWCOUNT

                                -- registrants without pages

                            INSERT #staging (                       AppealName,
                                                                    AppealWebDirectory,
                                                                    FirstName,
                                                                    ImageChoice,
                                                                    LastName,
                                                                    PhotoURL,
                                                                    ProfileWebDirectory,
                                                                    TotalRaisedOffline,
                                                                    TotalRaisedOnline,
                                                                    TotalContributions,
                                                                    DisplayPhoto,
                                                                    HasStockImages,
                                                                    LastUpdated)
                                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

                                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
                                    )

                                SET @TotalCount = @TotalCount + @@ROWCOUNT


                            -- out with the old, in with the new
                            BEGIN TRANSACTION

                                DELETE FROM BB02_OLR_GetSupportersCache WITH (XLOCK, ROWLOCK, HOLDLOCK)
                                 WHERE designeventid = @DesignEventId

                                INSERT INTO bb02_olr_getsupporterscache (DesignEventId,
                                                                        AppealName,
                                                                        AppealWebDirectory,
                                                                        FirstName,
                                                                        ImageChoice,
                                                                        LastName,
                                                                        PhotoURL,
                                                                        ProfileWebDirectory,
                                                                        TotalRaisedOffline,
                                                                        TotalRaisedOnline,
                                                                        TotalContributions,
                                                                        DisplayPhoto,
                                                                        HasStockImages,
                                                                        LastUpdated,
                                                                        TotalCount)

                                SELECT  DesignEventId = @DesignEventId,
                                        AppealName,
                                        AppealWebDirectory,
                                        FirstName,
                                        ImageChoice,
                                        LastName,
                                        PhotoURL,
                                        ProfileWebDirectory,
                                        TotalRaisedOffline,
                                        TotalRaisedOnline,
                                        TotalContributions,
                                        DisplayPhoto,
                                        HasStockImages,
                                        LastUpdated = CURRENT_TIMESTAMP,
                                        TotalCount = @TotalCount

                            COMMIT TRANSAcTION

                            -- don't drop #staging table yet, we'll re-use it for output again right away
                            SELECT @cache_was_updated = 1

                        END
                    ELSE
                        BEGIN
                            -- no lock was obtained, simply wait for the lock to be freed as that will 
                            -- be the moment the new data comes available

                            EXEC @rc = sp_getapplock @Resource = @LockName,
                                                     @LockMode = 'Exclusive',
                                                     @LockTimeout = 600000, -- 10 minutes should be enough, end-user will be pretty annyoyed anyway =)
                                                     @lockOwner = 'Session'


                        END


                    -- free the lock agian, we assume reading locks are handled properly 
                    EXEC sp_releaseapplock @Resource = @LockName


                END -- cache update required or not?

            -- fetch results
            SET @sql = Convert(nvarchar(max), N'')
                     + N'

            SELECT * FROM (
            select
                TotalCount' + (CASE WHEN @cache_was_updated  = 1 THEN ' = @TotalCount' ELSE N'' END) + ',
                SupporterId,
                AppealName,
                AppealWebDirectory,
                FirstName,
                ImageChoice,
                LastName,
                PhotoURL,
                ProfileWebDirectory,
                TotalRaisedOffline,
                TotalRaisedOnline,
                TotalContributions,
                DisplayPhoto,
                HasStockImages,     
                row_number() over (order by ' + 
                    case when @OrderByField IN ('FirstName', 'LastName') then @OrderByField 
                         when @OrderByField = 'TotalRaised'              then '(TotalRaisedOnline + TotalRaisedOffline)' 
                                                                         else 'AppealName'  end
                + ' ' + @OrderByDirection + ') as rownumber
            from ' + (CASE WHEN @cache_was_updated = 1 THEN '#staging' ELSE 'bb02_olr_getsupporterscache where designeventid = @DesignEventId' END) + '

            ) q
            where q.rownumber > @Offset
              and q.rownumber <= @Offset + @PageSize
            order by rownumber;'

            exec sp_executesql @stmt = @sql,
                               @params = N'@TotalCount int, @DesignEventId int, @Offset int, @PageSize int',
                               @TotalCount = @TotalCount,
                               @DesignEventId = @DesignEventId,
                               @Offset = @Offset,
                               @PageSize = @PageSize


            DROP TABLE #staging

        END


        -- other part not looked at (yet) as I guess the trouble comes from the refresh, and that's only in the first part as far as I can tell ...

    END