SQL 死锁 - 高流量

Posted

技术标签:

【中文标题】SQL 死锁 - 高流量【英文标题】:SQL deadlocks - high traffic 【发布时间】:2016-02-10 18:13:11 【问题描述】:

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

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

如果页面 5 分钟未命中,则根据 ID 从暂存表中删除,然后运行存储过程并将结果插入暂存表中。

然后使用“总结果计数”更新暂存表,根据 ID 从暂存表中选择

如您所见,它正在执行 DELETE、INSERT、UPDATE 和 SELECT。在负载下,我遇到了大量的死锁。

几个问题:

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

代码:

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

【问题讨论】:

我也可以提一个建议。在您的过程中使用 SET NOCOUNT ON。它将提高性能并且很容易获得:请参阅mssqltips.com/sqlservertip/1226/… 仔细查看 FROM 子句中项目的顺序。考虑放入子查询来强制查询编译器一个方向或另一个方向。如果一个 INNER JOIN 块比其他块更积极地减少结果集,那么将该块移近“FROM”将产生好处。 首先,您必须捕获真正的死锁并确定实际的死锁原因。我没有看到在这个 proc 内部开始任何事务 - 整个调用是否被事务包围?您应该将所有 select * 替换为必要的列名。首先检查@FundraiserCount@LastUpdated 可以替换为单个top。请注意,您在 top1 选择中缺少 ORDER BY 子句。并将Totalcount 更新移动到此事务之外。再说一次,直到你发现实际上是什么死锁 - 你无法修复它。 【参考方案1】:

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

也就是说,既然你已经掌握了这部分,我们来看看存储过程。

首先是一些一般性的评论:

我真的,真的不喜欢READ UNCOMMITTED。我知道你添加这个是为了避免锁定,但是脏读的想法真的让我很紧张。您可能会像这样向调用者返回一半或错误的信息。就我个人而言,我宁愿等待好消息而不是得到坏消息。 不要使用SELECT COUNT(*) ...来判断是否有记录。服务器需要扫描所有相关记录才能获得号码,但您真正感兴趣的只是是否有一个。为此请使用 WHERE EXISTS()。 更好,因为无论如何你都做了SELECT TOP 1,所以使用它的结果。如果找到了值,那么一定有记录! 当使用 datetime 字段/变量时,ISNULL(@LastUpdated, '') = '' 的构造有点可疑。而是使用@LastUpdated IS NULL 进行此检查 您执行DELETE,然后可能是 INSERT。我想这需要一段时间,这就是死锁的来源。适当的锁定在这里应该会有所帮助,但我不知道它如何与上面的READ UNCOMMITTED 配合。 在INSERT 之后,您再次检查新插入的数据以确定有多少行。由于这里唯一相关的行是我们刚刚插入的行,我们可以放心地假设 @@ROWCOUNT 已经拥有答案。读取该系统变量需要零努力。执行SELECT COUNT(*) ... 对系统的负担要大得多 就我个人而言,我更喜欢将INSERT 分成两部分并使用临时表来保存中间结果。我不确定它是否会对性能产生很大影响(理论上它对服务器来说是更多的工作),但我认为它会导致更少的惊喜,尤其是如果你在两者之间添加一些统计信息更新。只有测试才能真正说明问题。 拥有临时表的副作用是您可以简单地从所述表返回结果,而无需返回缓存表,避免与其他进程交互。 我还建议使用动态 sql,因为它为排序提供了灵活性。 `RowNumber()' 内部的套管结构确实有效,但我怀疑它是否过于高效。

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

对我在下面提出的代码进行一些快速而肮脏的更改。可能会有一些语法错误,甚至可能有一些逻辑错误,因此您可能需要根据需要进行调整/修复。但我希望它能为您指明正确的方向。

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

【讨论】:

以上是关于SQL 死锁 - 高流量的主要内容,如果未能解决你的问题,请参考以下文章

在高流量期间使用 couchdb 进行缓存和可扩展性

高并发高流量网站架构详解

高并发高流量网站架构

一条复杂SQL实现思路

架构设计 | 高并发流量削峰,共享资源加锁机制

架构设计 | 高并发流量削峰,共享资源加锁机制