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
,而另一个连接尝试执行DELETE
或UPDATE
等。这可不好。
为了避免这种情况,我会使用锁定,但即使在那里我也可能会遇到问题,所以我会尝试使用一个应用程序锁来“暂停”任何连接,而另一个连接仍在刷新缓存中的数据的过程中-桌子。更重要的是,我们可以将此锁定“限制”到正在处理的@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 死锁 - 高流量的主要内容,如果未能解决你的问题,请参考以下文章