使用并发插入避免SQL中的死锁

时间:2013-06-07 17:42:26

标签: sql sql-server linq c#-4.0

我有一个进程(进程A),它不断向SQL表添加记录(表A)(使用存储过程直接插入)。这是一个连续的过程,它读取请求并写入表。请求的方式没有模式。每天的最高要求约为100K。

请求进入后,我需要对这些请求进行一些处理。这些目前在用户桌面上完成(由于许可问题)。我目前正在做的方法是在每个用户上运行一个可执行文件(进程B),当请求进入表时,此进程读取并执行一些工作并写入同一个表。因此Table由多个进程读/写。流程B具有以下逻辑

  • 获取尚未由其他用户处理的记录,而不是 正在由另一个用户处理

    • 通过标记isProcessing标志(C#LINQ到SP)来锁定此运行的记录。这是一个单独的SQL事务,即锁定记录并将它们用于处理包含在事务中
  • 处理记录。这是计算发生的地方。这里没有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

2 个答案:

答案 0 :(得分:0)

我猜你在尝试并发更新时会在页面上发生死锁。

由于更新和插入的性质(基于getdate的滑动时间帧窗口),看起来很难实现好的分区方案。没有它,我认为你最好的选择是使用sp_getapplock实现应用程序级别锁(sql等效的互斥锁) http://msdn.microsoft.com/en-us/library/ms189823(v=sql.100).aspx

答案 1 :(得分:0)

我现在没时间分析您的工作量并找到真正的解决方案。所以我将添加一个不同类型的答案:您可以安全地重试死锁事务。只需重新运行整个事务即可解决此问题。在尝试重试之前,可能需要插入一点延迟。

请务必重新运行整个事务,包括应用程序中发生的任何控制流。如果重试,已读取的数据可能已更改。

如果重试很少,则这不是性能问题。您应该在重试发生时记录。