SQL Server-从TRANSACTION复制,SELECT / INSERT存在“唯一”值

时间:2015-03-08 16:23:15

标签: sql-server stored-procedures transactions

我是SQL事务管理的新手,我一直在阅读很多关于事务管理的内容,特别是并发问题和隔离级别。

我有一个相当'简单'的问题,我似乎无法解决 -

数据库有一个消费者表,其中包含主键,ConsumerID和应该唯一的索引(但不是唯一)字段SSN。我们有一个存储过程来处理传入的贷款请求,这些请求实际上有这个代码块来维护Consumer表:

    SELECT @ConsumerID = ConsumerID FROM Consumer WHERE SSN = @SSN
    IF @ConsumerID IS NULL
    BEGIN
        INSERT INTO Consumer (SSN, ...) VALUES (@SSN, ...)
        SELECT @ConsumerID = SCOPE_IDENTITY()
    END

我们一直在获得对存储过程的两个请求同时进入的实例,并且(当然)我们在Consumer表中获得了重复的SSN记录。制作SSN索引UNIQUE就在桌面上,但我们现在无法做到。解决方案是使用TRANSACTION:

    BEGIN TRANSACTION
    BEGIN TRY
        SELECT @ConsumerID = ConsumerID FROM Consumer WHERE SSN = @SSN
        IF @ConsumerID IS NULL
        BEGIN
            INSERT INTO Consumer (SSN, ...) VALUES (@SSN, ...)
            SELECT @ConsumerID = @SCOPE_IDENTITY
            COMMIT TRANSACTION
        END
    END TRY
    BEGIN CATCH
        ROLLBACK TRANSACTION
    END CATCH

现在,由于TRANSACTION,这肯定会阻止重复的SSN。但是,我通过在同时运行的两个C#进程中多次异步调用存储过程来进行并发测试。性能受到TRANSACTION的影响,大约为几秒钟。这是不可接受的,因为我们依赖速度作为我们系统响应的主要因素。

如何调整此TRANSACTION以缩短响应时间?我有一个预感它位于乐观并发的某个地方(我们使用SQL Server的默认值),但我不明白它是如何与这样一对SELECT - INSERT指令相关的。请记住,将SSN索引设为UNIQUE是可能的,但我们现在可能不希望这样做。

2 个答案:

答案 0 :(得分:2)

您可以使用独占锁来确保其他用户在查询和插入值时不能插入重复项......

BEGIN TRY
  BEGIN TRANSACTION;

    IF NOT EXISTS(SELECT 1 FROM Consumer WITH (UPDLOCK,HOLDLOCK) WHERE SSN = @SSN)
    BEGIN
        INSERT INTO Consumer (SSN, ...) VALUES (@SSN, ...)
        SELECT @ConsumerID = SCOPE_IDENTITY();
    END
  COMMIT TRANSACTION;
END TRY

BEGIN CATCH
 IF (@@TRANCOUNT <> 0)
     ROLLBACK TRANSACTION;
END CATCH

答案 1 :(得分:0)

  1. 您可以使用SQL Insert Trigger测试表并回滚将导致表中重复值的事务。比较它所用的时间。

  2. 如果无法使用上述选项。最小化事务内部的工作,如果不可能,那么优化SELECT&amp;的查询。 INSERT或事务内的任何其他查询。

  3. 查询执行时间将取决于表中存在的索引数,表中的记录数。 INSERT在没有索引或索引最小的情况下更快,而SELECT可能会受益匪浅。看一下Query执行计划。

    create table test(id int)
    Go
    
    Create TRIGGER test_insert_one  
    ON test  
    AFTER INSERT
    AS 
    Declare @Count int
    select @Count = count(id) from test where id = (select id from inserted)
    IF(@Count > 1)
        Rollback;
    GO  
    
    insert into test
    select 1
    GO
    insert into test
    select 1
    GO
    
    select * from test
    GO