以下是该方案:
我有一个名为MarketDataCurrent(MDC)的表,它具有实时更新股票价格。
我有一个名为'LiveFeed'的进程,它读取从线路流出的价格,排队插入,并使用'批量上传到临时表然后插入/更新到MDC表'。 (BulkUpsert)
我有另一个进程然后读取这些数据,计算其他数据,然后使用类似的BulkUpsert存储过程将结果保存回同一个表中。
第三,有很多用户在运行C#Gui轮询MDC表并从中读取更新。
现在,在数据快速变化的那一天,事情运行得相当顺利,但是在市场营业时间之后,我们最近开始看到越来越多的死锁异常从数据库出来,现在我们看到10-每天20。这里需要注意的重要一点是,当值不变时会发生这些。
以下是所有相关信息:
表格默认:
CREATE TABLE [dbo].[MarketDataCurrent](
[MDID] [int] NOT NULL,
[LastUpdate] [datetime] NOT NULL,
[Value] [float] NOT NULL,
[Source] [varchar](20) NULL,
CONSTRAINT [PK_MarketDataCurrent] PRIMARY KEY CLUSTERED
(
[MDID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
-
我有一个Sql Profiler跟踪正在运行,捕获死锁,这就是所有图形的样子。
进程258被重复地称为以下'BulkUpsert'存储过程,而73正在调用下一个:
ALTER proc [dbo].[MarketDataCurrent_BulkUpload]
@updateTime datetime,
@source varchar(10)
as
begin transaction
update c with (rowlock) set LastUpdate = getdate(), Value = t.Value, Source = @source
from MarketDataCurrent c INNER JOIN #MDTUP t ON c.MDID = t.mdid
where c.lastUpdate < @updateTime
and c.mdid not in (select mdid from MarketData where LiveFeedTicker is not null and PriceSource like 'LiveFeed.%')
and c.value <> t.value
insert into MarketDataCurrent
with (rowlock)
select MDID, getdate(), Value, @source from #MDTUP
where mdid not in (select mdid from MarketDataCurrent with (nolock))
and mdid not in (select mdid from MarketData where LiveFeedTicker is not null and PriceSource like 'LiveFeed.%')
commit
另一个:
ALTER PROCEDURE [dbo].[MarketDataCurrent_LiveFeedUpload]
AS
begin transaction
-- Update existing mdid
UPDATE c WITH (ROWLOCK) SET LastUpdate = t.LastUpdate, Value = t.Value, Source = t.Source
FROM MarketDataCurrent c INNER JOIN #TEMPTABLE2 t ON c.MDID = t.mdid;
-- Insert new MDID
INSERT INTO MarketDataCurrent with (ROWLOCK) SELECT * FROM #TEMPTABLE2
WHERE MDID NOT IN (SELECT MDID FROM MarketDataCurrent with (NOLOCK))
-- Clean up the temp table
DELETE #TEMPTABLE2
commit
为了澄清,这些临时表是由同一连接上的C#代码创建的,并使用C#SqlBulkCopy类进行填充。
对我而言,它看起来像是桌面PK上的死锁,所以我尝试删除那个PK并转而使用Unique Constraint,但这会使死锁数量增加10倍。
我完全不知道如何处理这种情况,并且对任何建议持开放态度。
HELP !!
响应XDL的请求,这里是:
<deadlock-list>
<deadlock victim="processc19978">
<process-list>
<process id="processaf0b68" taskpriority="0" logused="0" waitresource="KEY: 6:72057594090487808 (d900ed5a6cc6)" waittime="718" ownerId="1102128174" transactionname="user_transaction" lasttranstarted="2010-06-11T16:30:44.750" XDES="0xffffffff817f9a40" lockMode="U" schedulerid="3" kpid="8228" status="suspended" spid="73" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2010-06-11T16:30:44.750" lastbatchcompleted="2010-06-11T16:30:44.750" clientapp=".Net SqlClient Data Provider" hostname="RISKAPPS_VM" hostpid="3836" loginname="RiskOpt" isolationlevel="read committed (2)" xactid="1102128174" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="MKP_RISKDB.dbo.MarketDataCurrent_BulkUpload" line="28" stmtstart="1062" stmtend="1720" sqlhandle="0x03000600a28e5e4ef4fd8e00849d00000100000000000000">
UPDATE c WITH (ROWLOCK) SET LastUpdate = getdate(), Value = t.Value, Source = @source
FROM MarketDataCurrent c INNER JOIN #MDTUP t ON c.MDID = t.mdid
WHERE c.lastUpdate < @updateTime
and c.mdid not in (select mdid from MarketData where BloombergTicker is not null and PriceSource like 'Blbg.%')
and c.value <> t.value </frame>
<frame procname="adhoc" line="1" stmtstart="88" sqlhandle="0x01000600c1653d0598706ca7000000000000000000000000">
exec MarketDataCurrent_BulkUpload @clearBefore, @source </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">
unknown </frame>
</executionStack>
<inputbuf>
(@clearBefore datetime,@source nvarchar(10))exec MarketDataCurrent_BulkUpload @clearBefore, @source </inputbuf>
</process>
<process id="processc19978" taskpriority="0" logused="0" waitresource="KEY: 6:72057594090487808 (74008e31572b)" waittime="718" ownerId="1102128228" transactionname="user_transaction" lasttranstarted="2010-06-11T16:30:44.780" XDES="0x380be9d8" lockMode="U" schedulerid="5" kpid="8464" status="suspended" spid="248" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2010-06-11T16:30:44.780" lastbatchcompleted="2010-06-11T16:30:44.780" clientapp=".Net SqlClient Data Provider" hostname="RISKBBG_VM" hostpid="4480" loginname="RiskOpt" isolationlevel="read committed (2)" xactid="1102128228" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="MKP_RISKDB.dbo.MarketDataCurrentBlbgRtUpload" line="14" stmtstart="840" stmtend="1220" sqlhandle="0x03000600005f9d24c8878f00849d00000100000000000000">
UPDATE c WITH (ROWLOCK) SET LastUpdate = t.LastUpdate, Value = t.Value, Source = t.Source
FROM MarketDataCurrent c INNER JOIN #TEMPTABLE2 t ON c.MDID = t.mdid;
-- Insert new MDID </frame>
<frame procname="adhoc" line="1" sqlhandle="0x010006004a58132228bf8d73000000000000000000000000">
MarketDataCurrentBlbgRtUpload </frame>
</executionStack>
<inputbuf>
MarketDataCurrentBlbgRtUpload </inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594090487808" dbid="6" objectname="MKP_RISKDB.dbo.MarketDataCurrent" indexname="PK_MarketDataCurrent" id="lock5ba77b00" mode="U" associatedObjectId="72057594090487808">
<owner-list>
<owner id="processc19978" mode="U"/>
</owner-list>
<waiter-list>
<waiter id="processaf0b68" mode="U" requestType="wait"/>
</waiter-list>
</keylock>
<keylock hobtid="72057594090487808" dbid="6" objectname="MKP_RISKDB.dbo.MarketDataCurrent" indexname="PK_MarketDataCurrent" id="lock65dca340" mode="U" associatedObjectId="72057594090487808">
<owner-list>
<owner id="processaf0b68" mode="U"/>
</owner-list>
<waiter-list>
<waiter id="processc19978" mode="U" requestType="wait"/>
</waiter-list>
</keylock>
</resource-list>
</deadlock>
</deadlock-list>
答案 0 :(得分:2)
死锁似乎是关键访问顺序的直接死锁。一个简单的解释是两个批量更新操作之间的更新密钥的重叠。
一个不太重要的解释是,在SQL Server(以及其他服务器)中,锁定的密钥是哈希,并且存在(非常重要的)哈希冲突概率。这可以解释为什么你最近看到更多的死锁与之前相比:只是你的数据量增加了,因此碰撞概率增加了。如果这看起来很深奥且不太可能,请阅读%%lockres%% collision probability magic marker: 16,777,215,并从中链接文章。概率非常高,对于完美密钥分布,在仅约16M插入后,您有50%的碰撞概率。对于普通的,真实的世界,关键分布,你只有几千个插入物具有显着的碰撞概率。不幸的是,没有解决方法。如果这确实是问题,那么唯一的解决方案是减小批量的大小(#temp表的大小),以便降低碰撞概率。或者处理死锁并重试......无论如何你都必须这样做,但至少你可以处理更少的死锁。
答案 1 :(得分:1)
这是在主要工作时间之后发生的,数据没有变化,而且最近刚刚开始。服务器上最近有什么变化吗?我怀疑一些新的数据库维护工作可能会干扰。
顺便说一句,如果您知道市场已关闭且数据未发生变化,为什么您的流程仍在运行?
答案 2 :(得分:1)
我想回答我在评论中提出的一个问题,即
“你如何识别锁定的行?”。
在以下死锁XDL中,在锁定的两个“进程”节点上,存在waitresource
属性。在这种情况下:
waitresource="KEY: 6:72057594090487808 (d4005c04b35f)
和
waitresource="KEY: 6:72057594090487808 (b00072ea4ffd)
使用Remus指向的%%lockres%%
关键字,
select %%lockres%%, * from MarketDataCurrent
where %%lockres%% in ('(d4005c04b35f)', '(b00072ea4ffd)')
这产生了两行冲突。它们确实是独特的ids,没有碰撞。我仍然不知道为什么我在这里陷入僵局,但我越来越近了。
我会注意到两个id都应该只来自LiveFeed程序,但同样,更新中有一个子句应该从另一端实际更新过滤掉这一行。 / p>
<deadlock-list>
<deadlock victim="processffffffff8f5872e8">
<process-list>
<process id="process8dcb68" taskpriority="0" logused="1256" waitresource="KEY: 6:72057594090487808 (d4005c04b35f)" waittime="1906" ownerId="1349627324" transactionname="user_transaction" lasttranstarted="2010-06-16T16:50:04.727" XDES="0x424e6258" lockMode="U" schedulerid="2" kpid="1004" status="suspended" spid="683" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2010-06-16T16:50:04.727" lastbatchcompleted="2010-06-16T16:50:04.727" clientapp=".Net SqlClient Data Provider" hostname="RISKAPPS_VM" hostpid="2600" loginname="RiskOpt" isolationlevel="read committed (2)" xactid="1349627324" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="MKP_RISKDB.dbo.MarketDataCurrent_BulkUpload" line="28" stmtstart="1062" stmtend="1720" sqlhandle="0x03000600a28e5e4ef4fd8e00849d00000100000000000000">
UPDATE c WITH (ROWLOCK) SET LastUpdate = getdate(), Value = t.Value, Source = @source
FROM MarketDataCurrent c INNER JOIN #MDTUP t ON c.MDID = t.mdid
WHERE c.lastUpdate < @updateTime
and c.mdid not in (select mdid from MarketData where BloombergTicker is not null and PriceSource like 'Blbg.%')
and c.value <> t.value </frame>
<frame procname="adhoc" line="1" stmtstart="88" sqlhandle="0x01000600c1653d0598706ca7000000000000000000000000">
exec MarketDataCurrent_BulkUpload @clearBefore, @source </frame>
<frame procname="unknown" line="1" sqlhandle="0x000000000000000000000000000000000000000000000000">unknown</frame>
</executionStack>
<inputbuf>(@clearBefore datetime,@source nvarchar(10))exec MarketDataCurrent_BulkUpload @clearBefore, @source</inputbuf>
</process>
<process id="processffffffff8f5872e8" taskpriority="0" logused="0" waitresource="KEY: 6:72057594090487808 (b00072ea4ffd)" waittime="1921" ownerId="1349627388" transactionname="user_transaction" lasttranstarted="2010-06-16T16:50:04.757" XDES="0x289ea040" lockMode="U" schedulerid="5" kpid="11192" status="suspended" spid="382" sbid="0" ecid="0" priority="0" transcount="2" lastbatchstarted="2010-06-16T16:50:04.757" lastbatchcompleted="2010-06-16T16:50:04.757" clientapp=".Net SqlClient Data Provider" hostname="RISKBBG_VM" hostpid="2452" loginname="RiskOpt" isolationlevel="read committed (2)" xactid="1349627388" currentdb="6" lockTimeout="4294967295" clientoption1="671088672" clientoption2="128056">
<executionStack>
<frame procname="MKP_RISKDB.dbo.MarketDataCurrentBlbgRtUpload" line="14" stmtstart="840" stmtend="1220" sqlhandle="0x03000600005f9d24c8878f00849d00000100000000000000">
UPDATE c WITH (ROWLOCK) SET LastUpdate = t.LastUpdate, Value = t.Value, Source = t.Source
FROM MarketDataCurrent c INNER JOIN #TEMPTABLE2 t ON c.MDID = t.mdid;
</frame>
<frame procname="adhoc" line="1" sqlhandle="0x010006004a58132228bf8d73000000000000000000000000">
MarketDataCurrentBlbgRtUpload </frame>
</executionStack>
<inputbuf>
MarketDataCurrentBlbgRtUpload </inputbuf>
</process>
</process-list>
<resource-list>
<keylock hobtid="72057594090487808" dbid="6" objectname="MKP_RISKDB.dbo.MarketDataCurrent" indexname="PK_MarketDataCurrent" id="lock409d32c0" mode="U" associatedObjectId="72057594090487808">
<owner-list>
<owner id="processffffffff8f5872e8" mode="U"/>
</owner-list>
<waiter-list>
<waiter id="process8dcb68" mode="U" requestType="wait"/>
</waiter-list>
</keylock>
<keylock hobtid="72057594090487808" dbid="6" objectname="MKP_RISKDB.dbo.MarketDataCurrent" indexname="PK_MarketDataCurrent" id="lock706647c0" mode="U" associatedObjectId="72057594090487808">
<owner-list>
<owner id="process8dcb68" mode="U"/>
</owner-list>
<waiter-list>
<waiter id="processffffffff8f5872e8" mode="U" requestType="wait"/>
</waiter-list>
</keylock>
</resource-list>
</deadlock>
</deadlock-list>
答案 3 :(得分:0)
经过近两年恼人的死锁警告电子邮件后,我终于解决了这个问题。
我在竞争插页上使用FULL TABLE LOCKING解决了这个问题。我曾尝试将锁定减少到行级别,但锁定升级到表级别。最后,我认为该表足够小,即使很多用户每秒都在阅读和写入,但完全锁定是我愿意为数据一致性所做的一小部分性能打击。
此外,使用MERGE将插入/更新组合到一个原子语句允许我这样做。
这是已解决的生产代码(它有效!):
declare @date datetime;
set @date = getdate();
merge marketdatacurrent with (tablockx) as mdc
using #MDTUP as upload
on mdc.MDID = upload.MDID
when matched then
update
set mdc.lastupdate = @date,
mdc.value = upload.value,
mdc.source = @source
when not matched then
insert ( mdid, lastupdate, value, source )
values ( upload.mdid, @date, upload.value, @source);