我从tblinfo
表中获取日期值。如果日期为null,那么我将插入表中,否则我将更新表中的行。
我面临的问题是,同时使用相同信息访问表的2个会话都获得了Null
,因此两者都试图将记录插入到具有相同主键值的同一个表中。我们正在解决主键约束问题。如何阻止这个?
begin tran
set @date=(select date from tblinfo where id=@id)
if(@date is null)
--insert
else
--update
commit tran
答案 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