在高频插入时使用复合主键复制密钥冲突

时间:2017-09-13 14:59:40

标签: sql sql-server sql-server-2014

我有一张表格如下:

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与此相关

2 个答案:

答案 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)。