SQL Server 并发 Updlock Serializable 与 Try Catch

4 sql-server insert concurrency

我们每秒从不同的平面文件导入多条记录。有时我们会遇到赛车条件,并重复唯一错误约束。我们正在插入和检索记录,

我听说有两种方法可以解决这个问题。这是更好的方法,我听说UPDLOCK,SERIALIZABLE是标准方法。但是,try catch 会阻止检查附加的 If 语句。两种方式都是完全证明,并且会停止重复插入吗?什么是最佳编码实践明智的,并且表现更好?

CREATE TABLE dbo.Customer
(
    RowId bigint IDENTITY(1,1) NOT NULL,
    CustomerId guid NOT NULL,
    Name varchar(255) NOT NULL,
    CONSTRAINT PK_RowId PRIMARY KEY CLUSTERED([RowId] ASC)
)
create unique nonclustered index [UN_CustomerId] ON [dbo].[Customer] ([CustomerId] ASC) include (Name)
create nonclustered index [UN_Name] ON [dbo].[Customer] ([Name] ASC) include (CustomerId)
Run Code Online (Sandbox Code Playgroud)

方法一:

IF NOT EXISTS
(
    SELECT * 
    FROM dbo.Customer WITH (UPDLOCK, SERIALIZABLE) 
    WHERE Name = @Name
)
BEGIN
    INSERT INTO dbo.Customer(CustomerId, Name) VALUES (@CustomerId, @Name)
    SELECT @CustomerId
END
ELSE
BEGIN
    SELECT CustomerId FROM dbo.Customer WHERE Name = @Name
END
Run Code Online (Sandbox Code Playgroud)

方法二:

BEGIN TRY
    INSERT INTO dbo.Customer(CustomerId, Name) VALUES (@CustomerId, @Name)
    SELECT @CustomerId
END TRY
BEGIN CATCH
    SELECT CustomerId FROM dbo.Customer WHERE Name = @Name
END CATCH 
Run Code Online (Sandbox Code Playgroud)

Pau*_*ite 5

两种方式都是完全证明,并且会停止重复插入吗?

方法 2在并发下并不安全。当 catch 子句中的 select 运行时,无法保证导致插入失败的行将继续存在。

此外,catch 子句可以针对除重复键违规以外的错误执行,因为代码不检查错误号。

您还应该意识到注定交易失败的可能性。

Aaron Bertrand写了关于try/catch的开销。开销通常高于先检查。

什么是最佳编码实践明智的,并且表现更好?

方法 1 是一种常见的模式,但需要一个事务才能安全。性能取决于当地因素,因此您应该进行自己的测试。作为旁注,您可以通过使用 output 子句来避免一个查询:

DECLARE 
    @CustomerId uniqueidentifier = {guid '16D39773-9CC2-4CCF-A6A8-ACF1465030CC'},
    @Name varchar(255) = 'name';

BEGIN TRANSACTION;

    IF NOT EXISTS
    (
        SELECT * 
        FROM dbo.Customer WITH (UPDLOCK, SERIALIZABLE) 
        WHERE Name = @Name
    )
    BEGIN
        INSERT dbo.Customer(CustomerId, [Name])
        OUTPUT @CustomerId AS CustomerId
        VALUES (@CustomerId, @Name);
    END;
    ELSE
    BEGIN
        SELECT CustomerId FROM dbo.Customer WHERE [Name] = @Name;
    END;

COMMIT TRANSACTION;
Run Code Online (Sandbox Code Playgroud)

作为替代方案,您可能想要比较安全合并解决方案的性能:

DECLARE 
    @CustomerId uniqueidentifier = {guid '16D39773-9CC2-4CCF-A6A8-ACF1465030CC'},
    @Name varchar(255) = 'name';

MERGE dbo.Customer WITH (SERIALIZABLE) AS C
USING (VALUES(@CustomerId, @Name)) AS I (CustomerId, [Name])
    ON I.Name = C.Name
WHEN NOT MATCHED 
    THEN INSERT (CustomerId, [Name])
    VALUES (I.CustomerId, I.[Name])
WHEN MATCHED THEN UPDATE 
    SET @CustomerId = C.CustomerId,
        @Name = C.[Name]
OUTPUT @CustomerId AS CustomerId;
Run Code Online (Sandbox Code Playgroud)