Sql 在可序列化隔离级别事务中避免死锁?
我试图在SQL Server中实现一个事件源,但遇到了死锁问题 在我的设计中,事件按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
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之前更改隔离级别。