Fixing upsert issue with TRANSACTION ISOLATION LEVEL REPEATABLE READ? - sql-server

I have a SQL statement that does an update, and then if the ##ROWCOUNT is 0, it will insert. this is basically a MERGE in SQL 2008. We are running into situations where two threads are failing on the update simultaneously. It will attempt to insert the same key twice in a table. We are using the Default Transaction isolation level, Read Committed. Will changing the level to repeatable reads fix this or do I have to go all the way to Serializable to make this work? Here is some code:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
BEGIN TRAN;
UPDATE TableA
SET Duration = #duration
WHERE keyA = #ID
AND keyB = #IDB;
IF ##rowcount = 0
BEGIN
INSERT INTO TableA (keyA,keyB,Duration)
VALUES (#ID,#IDB,#duration);
END
COMMIT TRAN;
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;";

You would need to go all the way up to SERIALIZABLE.
Under REPEATABLE READ if the row does not exist then both UPDATE statements can run concurrently without blocking each other and proceed to do the insert. Under SERIALIZABLE the range where the row would have been is blocked.
But you should also consider leaving the isolation level at default read committed and putting a unique constraint on keyA,keyB so any attempts to insert a dupe fail with an error.

Related

How do nested transactions work with triggers and different isolation levels in SQL Server?

I need to answer two questions. Here's an overview:
I have 3 tables:
CREATE TABLE A(x INT)
CREATE TABLE B(x INT)
CREATE TABLE C(x INT)
and 2 triggers:
ALTER TRIGGER ATr ON A
FOR INSERT AS
BEGIN
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
INSERT INTO B
SELECT *
FROM inserted;
COMMIT
END
ALTER TRIGGER BTr ON B
FOR INSERT AS
BEGIN
INSERT INTO C
SELECT * FROM inserted;
END
and the INSERT triggering the ATr trigger INSERT INTO A VALUES(3) is done on REPEATABLE READ isolation level, and the default isolation level for this database is READ COMMITTED.
And I need to answer two questions:
On which isolation level will the ATr trigger execute?
On which isolation level will the BTr trigger execute (if at all)?
I have problem understanding how these nested transactions are going to work. Will any transaction inside a LEVEL SERIALIZABLE transaction ever execute? Are they even nested in this case? What happens if a lower isolation level is inside a higher isolation level or the other way around?
The behavior is documented here SET TRANSACTION ISOLATION LEVEL
And you can examine this behavior by running
select CASE transaction_isolation_level
WHEN 0 THEN 'Unspecified'
WHEN 1 THEN 'ReadUncomitted'
WHEN 2 THEN 'ReadCommitted'
WHEN 3 THEN 'Repeatable'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot' END AS transaction_isolation_level
from sys.dm_exec_requests
where session_id = ##spid
wherever you are curious about the currently-active isolation level.

Lost update in snapshot vs all the rest isolation levels

Let's suppose we use create new table and enable snapshot isolation for our database:
alter database database_name set allow_snapshot_isolation on
create table marbles (id int primary key, color char(5))
insert marbles values(1, 'Black') insert marbles values(2, 'White')
Next, in session 1 begin a snaphot transaction:
set transaction isolation level snapshot
begin tran
update marbles set color = 'Blue' where id = 2
Now, before committing the changes, run the following in session 2:
set transaction isolation level snapshot
begin tran
update marbles set color = 'Yellow' where id = 2
Then, when we commit session 1, session 2 will fail with an error about transaction aborted - I understand that is preventing from lost update.
If we follow this steps one by one but with any other isolation level such as: serializable, repeatable read, read committed or read uncommitted this Session 2 will get executed making new update to our table.
Could someone please explain my why is this happening?
For me this is some kind of lost update, but it seems like only snapshot isolation is preventing from it.
Could someone please explain my why is this happening?
Because under all the other isolation levels the point-in-time at which the second session first sees the row is after the first transaction commits. Locking is a kind of time travel. A session enters a lock wait and is transported forward in time to when the resource is eventually available.
For me this is some kind of lost update
No. It's not. Both updates were properly completed, and the final state of the row would have been the same if the transactions had been 10 minutes apart.
In a lost update scenario, each session will read the row before attempting to update it, and the results of the first transaction are needed to properly complete the second transaction. EG if each is incrementing a column by 1.
And under locking READ COMMITTED, REPEATABLE READ, and SERIALIZABLE the SELECT would be blocked, and no lost update would occur. And under READ_COMMITTED_SNAPSHOT the SELECT should have a UPDLOCK hint, and it would block too.

tSQL 2012 - table locking within TRANSACTION

I have the following TRANSACTION structure:
BEGIN TRY
BEGIN tran sometransaction
INSERT INTO local_table_1 (columns...)
SELECT (columns...)
FROM remote_table
WHERE (conditions, predicates)
UPDATE local_table_1
SET column_A = value
WHERE....
UPDATE local_table_1
SET column_B = value
WHERE....
UPDATE local_table_1
SET column_C = value
WHERE....
COMMIT tran sometransaction
END TRY
BEGIN catch
ROLLBACK tran sometransaction
END catch
I want to make sure that no one is allowed to read contents of local_table_1 unless all statements within this transaction are over and have been committed.
Is there a way to set WITH (TABLOCKX, HOLDLOCK) on the whole transaction? I understand that tables lock automatically during the transaction execution however I could not find any explanation if that extends on external concurrent read processes.
Thank you.
An external process will not read uncommitted transactions unless your external process is using a NOLOCK hint or READ UNCOMMITTED isolation. This is all related to Isolation in reference to the ACID properties of sql. http://en.wikipedia.org/wiki/ACID
Set the transaction isolation level to READ COMMITTED or REPEATABLE READ before you begin your transaction. The following link has a description and example:
http://msdn.microsoft.com/en-us/library/ms173763.aspx

Lock a row in a table SQL Server

I need to lock a row in a table so no one can read this line while I'm running a procedure. I am using BEGIN TRAN in this procedure. So, this record I'm trying to block is uncommitted during the process.
Is it possible?
Depending on what is the purpose of your stored procedure:
- In case it modifies the mentioned row, you can base on transaction levels
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
--UPDATE/INSERT/DELETE your row here
...
COMMIT TRANSACTION
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ
- Use lock hints
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION
SELECT column1, column2
FROM yourTable WITH (ROWLOCK)
WHERE ID = YourRecordId
...
COMMIT TRANSACTION
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

What is the scope of isolation in nested transactions in SQL Server?

Consider the following SQL:
BEGIN TRAN
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
INSERT Bands
( Name )
SELECT 'Depeche Mode'
UNION
SELECT 'Arcade Fire'
-- I've indented the inner transaction to make it clearer.
BEGIN TRAN
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
SELECT *
FROM Bands
COMMIT
-- What is the isolation level right here?
UPDATE Bands
SET Name = 'Modest Mouse'
WHERE Name = 'Oddest House'
COMMIT
In sum, we start a transaction and set its isolation level to READ COMMITTED. We then do some random SQL and start another, nested transaction. In this transaction we change the isolation level to READ UNCOMMITTED. We then commit that transaction and return to the other.
Now, my guess is that after the inner commit, the isolation level returns to READ COMMITTED. Is this correct?
You [Bob Probst] are correct. Interestingly, according to the documentation you linked:
If you issue SET TRANSACTION ISOLATION LEVEL in a stored procedure or trigger, when the object returns control the isolation level is reset to the level in effect when the object was invoked. For example, if you set REPEATABLE READ in a batch, and the batch then calls a stored procedure that sets the isolation level to SERIALIZABLE, the isolation level setting reverts to REPEATABLE READ when the stored procedure returns control to the batch.
So, the bottom line here is that SET TRANSACTION ISOLATION LEVEL has procedure affinity, not transaction affinity (as I had thought).
Awesome!
I don't think that is correct.
Refer to the remarks here: Set Transaction
Only one of the isolation level
options can be set at a time, and it
remains set for that connection until
it is explicitly changed.

Resources