Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/sql/78.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Sql 在可序列化隔离级别事务中避免死锁?_Sql_Sql Server_Transactions_Sqltransaction - Fatal编程技术网

Sql 在可序列化隔离级别事务中避免死锁?

Sql 在可序列化隔离级别事务中避免死锁?,sql,sql-server,transactions,sqltransaction,Sql,Sql Server,Transactions,Sqltransaction,我试图在SQL Server中实现一个事件源,但遇到了死锁问题 在我的设计中,事件按DatasetId进行分组,每个事件都使用SequenceId编写,要求对于给定的DatasetId,SequenceId是串行的,从1开始,每次随每个事件增加一个,从不丢失一个值,也从不重复一个值 我的事件表类似于: CREATE TABLE [Events] ( [Id] [BIGINT] IDENTITY(1,1) NOT NULL, [DatasetId] [BIGINT] NOT NUL

我试图在SQL Server中实现一个事件源,但遇到了死锁问题

在我的设计中,事件按
DatasetId
进行分组,每个事件都使用
SequenceId
编写,要求对于给定的
DatasetId
SequenceId
是串行的,从1开始,每次随每个事件增加一个,从不丢失一个值,也从不重复一个值

我的
事件表类似于:

CREATE TABLE [Events]
(
    [Id] [BIGINT] IDENTITY(1,1) NOT NULL,
    [DatasetId] [BIGINT] NOT NULL,
    [SequenceId] [BIGINT] NOT NULL,
    [Value] [NVARCHAR](MAX) NOT NULL
)
我在
DatasetId
列上还有一个非唯一的非聚集索引

为了在该表中插入对
SequenceId
的上述限制,我一直在使用可序列化隔离级别在事务下插入行,并在此事务中手动计算所需的
SequenceId
,作为所有现有
SequenceId
的最大值加上一:

DECLARE @DatasetId BIGINT = 1, @Value NVARCHAR(MAX) = N'I am an event.';

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION

BEGIN TRY

    DECLARE @SequenceId BIGINT;
    SELECT @SequenceId = ISNULL(MAX([SequenceId]), 0) + 1
    FROM [Events]
    WHERE [DatasetId] = @DatasetId;

    INSERT INTO [Events] ([DatasetId], [SequenceId], [Value])
    VALUES (@DatasetId, @SequenceId, @Value);

    COMMIT TRANSACTION;

END TRY

BEGIN CATCH

    ROLLBACK TRANSACTION;

END CATCH
就我在
SequenceId
列上所要求的保证而言,这很有效。但是,我在尝试并行插入多行时遇到了死锁,即使这些行用于不同的
DatasetId
s

其行为似乎是,在第一个连接中生成
SequenceId
的查询会阻止在第二个连接中生成
SequenceId
的相同查询,而这第二次尝试会阻止第一个连接插入行的能力,这意味着两个事务都无法完成,因此出现死锁

是否有一种方法可以避免这种情况,同时仍然可以获得可序列化事务隔离级别的好处


我一直在考虑的另一种技术是降低隔离级别,而是使用
sp_getapplock
为给定的
DatasetId
手动获取表上的锁,这意味着我可以确保生成一致的
SequenceId
。一旦事务提交/回滚,锁将自动释放。这种方法合理吗,或者像这样手动管理锁被认为是一种反模式吗?

您可能需要阅读-因为它不会停止并发查询的并发操作,它只指定结果必须是什么。因此,它不能保证避免死锁。如果您使用的是
MAX([SequenceId])
,那么在获取值和设置值时,您确实希望成为表的独占用户。因此,为事务获取独占表锁是一种选择,就像使用
sp_getapplock
一样,尽管上次我尝试在这种情况下使用
sp_getapplock
时,它给了我死锁。所以我又回到了一个独占表锁。似乎您错过了[Events]中SELECT@SequenceId=ISNULL(MAX([SequenceId]),0)+1中的DatasetId=@DatasetId条件;另外,我认为read committed对于您的场景来说已经足够好了。只需在SELECT中为@SequenceIDALS添加提示HOLDLOCK,因此需要在启动trx之前更改隔离级别。