Read Snapshot Isolation level – use a one phase update

Switch to RCSI the focus is often on the server, can tempdb handle the rowversion load. But is the application also suitable for RSCI? Advise: always keep the update in one phase.

The code is in the end.

So here are three demo’s.

a) Read Committed and where is the lock

Execute the first part, not the commit.

b) Read Committed Snapshot and where is the lock (fine, but is our data still correct?)

Now the update gets blocked, the data was read from the RowVersion.

c) Read Committed Snapshot and where is the lock (WITH(READCOMMITTEDLOCK) data correct)

/* Thank you Erik Darling Data 
https://www.youtube.com/watch?v=tR_4yzccsvQ 

Put on RCSI 
a) can tempdb handle it? Yes nice .. less (dead)locking?
b) can the application handle it? Testing!, Testing!! 
*/

use StackOverflow2013
go

-- Off
alter database StackOverflow2013 
set read_committed_snapshot OFF 
with rollback immediate;

/*
READ_COMMITTED_SNAPSHOT is *not* isolation level, it is a database's option permitting 
to change the behavior of ReadCommitted isolation level database-wide.
Snapshot is RowVersioning.
*/

SELECT CASE  
          WHEN transaction_isolation_level = 1 
             THEN 'READ UNCOMMITTED' 
          WHEN transaction_isolation_level = 2 
               AND is_read_committed_snapshot_on = 1 
             THEN 'READ COMMITTED SNAPSHOT' 
          WHEN transaction_isolation_level = 2 
               AND is_read_committed_snapshot_on = 0 THEN 'READ COMMITTED' 
          WHEN transaction_isolation_level = 3 
             THEN 'REPEATABLE READ' 
          WHEN transaction_isolation_level = 4 
             THEN 'SERIALIZABLE' 
          WHEN transaction_isolation_level = 5 
             THEN 'SNAPSHOT' 
          ELSE NULL
       END AS TRANSACTION_ISOLATION_LEVEL 
FROM   sys.dm_exec_sessions AS s
       CROSS JOIN sys.databases AS d
WHERE  session_id = @@SPID
  AND  d.database_id = DB_ID();

/*
TRANSACTION_ISOLATION_LEVEL 
-> READ COMMITTED
*/

/* On */
alter database StackOverflow2013 
set read_committed_snapshot ON
with rollback immediate;

SELECT CASE  
          WHEN transaction_isolation_level = 1 
             THEN 'READ UNCOMMITTED' 
          WHEN transaction_isolation_level = 2 
               AND is_read_committed_snapshot_on = 1 
             THEN 'READ COMMITTED SNAPSHOT' 
          WHEN transaction_isolation_level = 2 
               AND is_read_committed_snapshot_on = 0 THEN 'READ COMMITTED' 
          WHEN transaction_isolation_level = 3 
             THEN 'REPEATABLE READ' 
          WHEN transaction_isolation_level = 4 
             THEN 'SERIALIZABLE' 
          WHEN transaction_isolation_level = 5 
             THEN 'SNAPSHOT' 
          ELSE NULL
       END AS TRANSACTION_ISOLATION_LEVEL 
FROM   sys.dm_exec_sessions AS s
       CROSS JOIN sys.databases AS d
WHERE  session_id = @@SPID
  AND  d.database_id = DB_ID();

/*
TRANSACTION_ISOLATION_LEVEL
-> READ COMMITTED SNAPSHOT
*/

/* OUT */
drop table if exists dbo.DinnerPlans;
create table dbo.DinnerPlans (
id bigint not null primary key clustered identity
, name nvarchar(40) NULL
, seat_number tinyint null
, is_free bit not null default 'false'
);

/* IN */
insert into dbo.DinnerPlans with (tablockx)
(name, seat_number, is_free)
select top(6)
name = case m.message_id
	when 21 then null
	when 101 then N'Lucky fan #1'
	when 102 then N'Lucky fan #2'
	when 103 then N'Lucky fan #3'
	when 104 then N'Lucky fan #4'
	when 105 then N'Lucky fan #5'
	end 
	, seat_number = row_number() over (order by 1/0)
	, case m.severity when 20 then 1 else 0 end
	from sys.messages as m
	where m.language_id = 1033
	order by m.message_id;

select * 
from dbo.DinnerPlans;

/* #1 is empty seat
id	name			seat_number	is_free
1	NULL			1			1
2	Lucky fan #1	2			0
3	Lucky fan #2	3			0
4	Lucky fan #3	4			0
5	Lucky fan #4	5			0
6	Lucky fan #5	6			0
*/

/* Need this in a new window te reset from snapshot */
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

BEGIN TRANSACTION
DECLARE @name nvarchar(40) = N'ronald';

with FirstFreeTable as (
select top(1) dp.id
from dbo.DinnerPlans as dp 
where dp.is_free = 1
)
update dp 
set dp.[name] = @name
, dp.is_free = 0
output inserted.*
from dbo.DinnerPlans as dp
JOIN FirstFreeTable as fft
on fft.id = dp.id;
commit transaction;

/* Need this in a new OTHER window te reset from snapshot */
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

BEGIN TRANSACTION
DECLARE @name nvarchar(40) = REVERSE(N'ronald');

with FirstFreeTable as (
select top(1) dp.id      
from dbo.DinnerPlans as dp
where dp.is_free = 1
)
update dp					
set dp.[name] = @name
, dp.is_free = 0
output inserted.*
from dbo.DinnerPlans as dp
JOIN FirstFreeTable as fft
on fft.id = dp.id;
commit transaction;

Geef een reactie

Je e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *