Reputation: 109
I am getting a date value from a tblinfo
table. If the date is null, then I will insert into table, else I will update the row in the table.
I am facing the issue that 2 sessions accessing the table simultaneously with same information, both are getting Null
, so both are trying to insert records into same table with same primary key value. We are getting the primary key constraints issue. How to stop this?
begin tran
set @date=(select date from tblinfo where id=@id)
if(@date is null)
--insert
else
--update
commit tran
Upvotes: 0
Views: 2704
Reputation: 6693
Right after "begin tran", update your table like below script
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
this will cause SQL Server lock the table and the second transaction will wait reading the data until the first process finish.
Upvotes: 1
Reputation: 1381
Relying on lock hints to get the desired outcome will perhaps prevent you from attempting to insert the value twice, but it won't prevent errors from happening. You'll just get an 'Unable to acquire lock' or deadlock error instead.
You should put a mutex in your code, in the minimally sized critical section. Using sp_getApplock, you can get a lock on a section of code with a wait period that you specify (subsequent threads will wait for the lock to clear and then continue). Here's some sample code:
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
Upvotes: 0
Reputation: 24490
Using Locking
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
Technique Avoiding Locking
If preventing reads from occurring is an issue for some reason, the below is a lockless approach; but requires an extra field in your database.
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
Upvotes: 4