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

anhgbhbe  于 2022-12-06  发布在  其他
关注(0)|答案(2)|浏览(126)

我有一个进程(进程A),它不断地向SQL表(表A)添加记录(使用存储过程直接插入)。这是一个连续的进程,它读取请求并写入表。请求是如何到来的没有模式。每天的最大请求数约为100 K。
请求进入后,我需要对这些请求进行一些处理。(由于许可问题)。我目前的做法是使用可执行文件(进程B)在每个用户上运行,这个进程读取和执行一些工作,并写入同一个表。因此,表由多个进程读取/写入。进程B具有以下逻辑

  • 获取尚未由其他用户处理且当前未由其他用户处理的记录
  • 通过将标志标记为正在处理(C# LINQ到SP)来锁定此运行的记录。这是单个SQL事务,即锁定记录并获取它们以进行处理 Package 在一个事务中
  • 处理记录。这是计算发生的地方。这里没有数据库工作。
  • 在表A中插入/更新记录(C# LINQ通过db.submitchanges)。这是死锁发生的地方。这是一个单独的SQL事务。

有时,我在写入表时会看到死锁。此SQL Server 2008(隔离级别为Read committed)。对SQL的访问是通过存储过程和直接C# Linq查询来完成的。问题是如何避免死锁。是否有更好的整体体系结构?也许,与其让所有这些子进程独立地写入表,我应该将它们发送到一个服务,该服务将它们排队并写入表?。我知道在没有所有代码的情况下很坚韧回答(只是太多了,无法显示),但希望我已经解释过了,我很乐意回答任何具体的问题。
这是一个典型的表结构。

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]

这是获取要处理的数据的proc。

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
cyvaqqii

cyvaqqii1#

我猜您在尝试并发更新时会在页面上出现死锁。
由于更新和插入的性质(基于getdate的滑动时间范围窗口),似乎很难实现一个好的分区方案。

bmp9r5qi

bmp9r5qi2#

我现在没有时间来分析你的工作量并找到真正的解决办法。所以我将添加一个不同的答案:**您可以安全地重试死锁事务。**只需重新运行整个事务即可修复此问题。在尝试重试之前,可能需要插入一点延迟。
请务必重新运行整个事务,包括应用程序中发生的任何控制流。在重试的情况下,已经读取的数据可能已经更改。
如果很少重试,这不是性能问题。您可能应该记录重试发生的时间。

相关问题