如何避免SQL Server中的并发读访问问题?

时间:2016-05-24 22:22:01

标签: sql sql-server sql-server-2008

我从tblinfo表中获取日期值。如果日期为null,那么我将插入表中,否则我将更新表中的行。

我面临的问题是,同时使用相同信息访问表的2个会话都获得了Null,因此两者都试图将记录插入到具有相同主键值的同一个表中。我们正在解决主键约束问题。如何阻止这个?

begin tran
     set @date=(select date from tblinfo where id=@id)
        if(@date is null) 
        --insert 
    else 
    --update 
commit tran

3 个答案:

答案 0 :(得分:2)

使用锁定

declare @Date DateTime
, @RowId bigint = 10

Begin Tran
select @Date = SomeDate 
from tblInfo 
with (RowLock) --this is the important point; during the transaction, this row will be locked, so other statements won't be able to read it
where Id = @RowId

if (@Date is Null)
begin
    insert SomeTable (TblInfoId, Date) 
    values (@RowId, GetUtcDate())
end
else
begin
    update SomeTable 
    set Date = @Date 
    where tblInfoId = @RowId
end
if @@TranCount > 0 Commit Tran

技术避免锁定

如果出于某种原因阻止读取发生是一个问题,则以下是无锁方法;但是在数据库中需要一个额外的字段。

declare @Date DateTime
, @RowId bigint = 10
, @DataAvailable bit

Begin Tran

update tblInfo
set session_lock = @@SPID
where Id = @RowId
and session_lock is null

select @Date = SomeDate 
, @DataAvailable = 1
from tblInfo 
where Id = @RowId
and session_lock = @@SPID

if (@DataAvailable = 1)
begin
    if (@Date is Null)
    begin
        insert SomeTable (TblInfoId, Date) 
        values (@RowId, GetUtcDate())
    end
    else
    begin
        update SomeTable 
        set Date = @Date 
        where tblInfoId = @RowId
    end
    update tblInfo
    set session_lock = null
    where Id = @RowId
    and session_lock = @@SPID
end
--optionally do something if the row was already "locked"
--else
--begin
    --...
--end
if @@TranCount > 0 Commit Tran

答案 1 :(得分:0)

在"开始转换"之后,像下面的脚本一样更新你的表

begin tran 

-- this update will lock the table so no other process can read data
update tblInfo with (tablock)
set date = date


---- do what ever you need to do here
set @date=(select * from tblinfo)

if(@date is null) 
    --insert 
else 
    --update 

commit tran

这将导致SQL Server锁定表,第二个事务将等待读取数据,直到第一个进程完成。

答案 2 :(得分:0)

依靠锁定提示来获得所需的结果可能会阻止您尝试两次插入值,但它不会阻止错误发生。你只是得到一个“无法获得锁定”的权利。或者死锁错误。

您应该在代码中放置一个互斥锁,在最小尺寸的关键部分。使用sp_getApplock,您可以使用您指定的等待时间来锁定代码段(后续线程将等待锁定清除然后继续)。这是一些示例代码:

declare @LockResource nvarchar(100) = 'VALUE_INSERT'    
begin transaction

    -- Fetch the lock:
    EXEC @res = sp_getapplock                 
                    @Resource = @LockResource,
                    @LockMode = 'Exclusive',
                    @LockOwner = 'Transaction',
                    @LockTimeout = 10000,
                    @DbPrincipal = 'public'

    if @res not in (0, 1)
    begin
        set @msg = 'Unable to acquire Lock for ' + @LockResource
        raiserror (@msg , 16, 1)
        rollback transaction
        return
    end 
    else
    begin
        begin try
            ---------------------------------------------
            -- Fetch value if it exists, insert if not.
            -- Both need to happen here.
            ---------------------------------------------
        end try
        begin catch
            select @msg = 'An error occurred:  ' + ERROR_MESSAGE()

            -- Release the lock.
            EXEC @res = sp_releaseapplock 
                    @Resource = @LockResource,
                    @DbPrincipal = 'public',
                    @LockOwner = 'Transaction' 

            rollback transaction
            raiserror(@msg, 11, 1)
            goto cleanup
        end catch

        -- Release the lock.
        EXEC @res = sp_releaseapplock 
                @Resource = @LockResource,
                @DbPrincipal = 'public',
                @LockOwner = 'Transaction' 
    end

    commit transaction