我有一个进程(进程A),它不断向SQL表添加记录(表A)(使用存储过程直接插入)。这是一个连续的过程,它读取请求并写入表。请求的方式没有模式。每天的最高要求约为100K。
请求进入后,我需要对这些请求进行一些处理。这些目前在用户桌面上完成(由于许可问题)。我目前正在做的方法是在每个用户上运行一个可执行文件(进程B),当请求进入表时,此进程读取并执行一些工作并写入同一个表。因此Table由多个进程读/写。流程B具有以下逻辑
获取尚未由其他用户处理的记录,而不是 正在由另一个用户处理
处理记录。这是计算发生的地方。这里没有db工作。
在表A中插入/更新记录(C#LINQ到db.submitchanges)。这是发生死锁的地方。这是一个单独的SQL事务。
偶尔,我在写表时会看到死锁。此SQL Server 2008(隔离级别为Read committed)。存储过程和直接C#Linq查询都可以访问SQL。 问题是如何避免死锁。有更好的整体架构吗?也许,而不是所有这些子进程独立写入表,我应该将它们发送到一个服务,将它们排队并写入表中?我知道很难回答没有所有代码(只是太多无法显示)但希望我已经解释过了,我很乐意回答任何具体问题。
这是一个有代表性的表结构。
CREATE TABLE [dbo].[tbl_data](
[tbl_id] [nvarchar](50) NOT NULL,
[xml_data] [xml] NULL, -- where output will be stored
[error_message] [nvarchar](250) NULL,
[last_processed_date] [datetime] NULL,
[last_processed_by] [nvarchar](50) NULL,
[processing_id] [uniqueidentifier] NULL,
[processing_start_date] [datetime] NULL,
[create_date] [datetime] NOT NULL,
[processing_user] [nvarchar](50) NULL,
CONSTRAINT [PK_tbl_data] PRIMARY KEY CLUSTERED
(
[tbl_id] ASC,
[create_date] ASC
) ON [PRIMARY]
这是获取处理数据的过程。
begin tran
-- clear processing records that have been running for more than 6 minutes... they need to be reprocessed...
update tbl_data set processing_id = null, processing_start_date = null
where DATEDIFF(MINUTE, processing_start_date, GETDATE()) >=6
DECLARE @myid uniqueidentifier = NEWID();
declare @user_count int
-- The literal number below is the max any user can process. The last_processed_by and last_processed_date are updated when a record has been processed
select @user_count = 5000 - count(*) from tbl_data where last_processed_by = @user_name and DATEDIFF(dd, last_processed_date, GETDATE()) = 0
IF (@user_count > 1000)
SET @user_count = 1000 -- no more than 1000 requests in each batch.
if (@user_count < 0)
set @user_count = 0
--mark the records as being processed
update tbl_data set processing_id = @myid, processing_start_date = GETDATE(), processing_user = @user_name from tbl_data t1 join
(
select top (@user_count) tbl_id from tbl_data
where
[enabled] = 1 and processing_id is null
and isnull(DATEDIFF(dd, last_processed_date, GETDATE()), 1) > 0
and isnull(DATEDIFF(dd, create_date, GETDATE()), 1) = 0
) t2 on t1.tbl_id = t2.tbl_id
-- get the records that have been marked
select tbl_id from tbl_data where processing_id = @myid
commit tran
答案 0 :(得分:0)
我猜你在尝试并发更新时会在页面上发生死锁。
由于更新和插入的性质(基于getdate的滑动时间帧窗口),看起来很难实现好的分区方案。没有它,我认为你最好的选择是使用sp_getapplock实现应用程序级别锁(sql等效的互斥锁) http://msdn.microsoft.com/en-us/library/ms189823(v=sql.100).aspx
答案 1 :(得分:0)
我现在没时间分析您的工作量并找到真正的解决方案。所以我将添加一个不同类型的答案:您可以安全地重试死锁事务。只需重新运行整个事务即可解决此问题。在尝试重试之前,可能需要插入一点延迟。
请务必重新运行整个事务,包括应用程序中发生的任何控制流。如果重试,已读取的数据可能已更改。
如果重试很少,则这不是性能问题。您应该在重试发生时记录。