我有一张表格如下:
CREATE TABLE [Alg].[Sequence](
[Id] [int] IDENTITY(1,1) NOT NULL,
[SequenceId] [int] NOT NULL,
[CustomerId] [bigint] NOT NULL,
[Data] [text] NULL,
CONSTRAINT [PK_Sequence] PRIMARY KEY CLUSTERED
(
[SequenceId] ASC,
[CustomerId] ASC
)
这是一个来自另一个表的插入/更新触发器的块,我将数据插入Sequence
表:
--insert data into sequence table
SELECT @MaxSeqId = ISNULL(MAX(SequenceId),0)
FROM Alg.[Sequence] WITH (ROWLOCK)
WHERE CustomerId = @CustomerId
INSERT INTO Alg.[Sequence]
VALUES (
@MaxSeqId + 1
,@CustomerId
,@SendingData
,GETDATE()
)
因此,每当高频插入进程(在带触发器的表上)时,就会发生违反重复键错误的情况。我试过了ROWLOCK
,但没有用。我怎样才能防止这种情况发生?
更新
我被问到为什么我没有使用内置序列,我试过但是找不到如何使用复合主键的序列。我不希望SequenceId列是标识符,事实上,我想将SequenceId作为每个CustomerId的标识。您可以看到我的另一个question与此相关
答案 0 :(得分:3)
这是一个糟糕的设计。你应该真的使用序列。真。
这是一个糟糕的设计的原因之一是,它很容易得到像你所拥有的错误。以下是如何使代码实际运行:
begin transaction
SELECT @MaxSeqId = ISNULL(MAX(SequenceId),0)
FROM Alg.[Sequence] WITH (ROWLOCK, UPDLOCK, SERIALIZABLE)
WHERE CustomerId = @CustomerId
INSERT INTO Alg.[Sequence](SequenceId, CustomerId, Data)
VALUES (
@MaxSeqId + 1
,@CustomerId
,@SendingData
)
commit transaction
UPDLOCK指示SQL Server对读取的行设置限制性锁定,并忽略版本存储。 SERIALIZABLE指示SQL使用范围锁定,这样即使没有找到行,也会对键范围进行U锁定,以防止并发SELECT在第一个会话执行INSERT并提交事务之前发现没有行。
稍微更好的模式是在序列生成器周围使用Application Lock,在生成密钥之前调用sp_getapplock,然后立即调用sp_releaseapplock。然后,在生成下一个键值之前,并发会话不必等到第一个会话的事务提交。
答案 1 :(得分:1)
您可以尝试以下内容:
SELECT @MaxSeqId = ISNULL(MAX(SequenceId),0)
FROM Alg.[Sequence] WITH (ROWLOCK, UPDLOCK, HOLDLOCK)
WHERE CustomerId = @CustomerId
INSERT INTO Alg.[Sequence]
VALUES (
@MaxSeqId + 1
,@CustomerId
,@SendingData
,GETDATE()
)
这将在序列表(范围锁定或表锁定)上保持锁定,直到事务完成。确保它在事务中运行。如果你说这是从触发器内部运行,这应该是隐含的。
请注意,以这种方式确定最大ID效率不高。如果您不记得偶尔的编号差距,那么IDENTITY
列就足够了。如果你不想要差距,也许反击表可以提供帮助(例如here)。