My test table:
CREATE TABLE [dbo].[Personel](
[PersonelID] [int] NOT NULL,
[Name] [nchar](10) NULL,
CONSTRAINT [PK_Personel] PRIMARY KEY CLUSTERED
(
[PersonelID] ASC
)
)
My Test Data:
insert into Personel
values (1, 'Jack')
, (2, 'John')
, (3, 'Kevin')
Connection A:
begin tran
update Personel
set Name = 'Michael'
where PersonelID = 1
Connection B:
SET TRANSACTION ISOLATION LEVEL ????
SELECT Name
FROM Personel WITH (????)
where PersonelID = 1
Connection A starts a transaction and is trying to update data, but transaction is still going on. Connection B tries to read the data that is being updated.
Is there a way (an Isolation Level or a hint or combination of these two) to see the original data (Jack, not Michael) before the transaction is committed or rolled back?
You can access the old version of the data in the SNAPSHOT isolation level.
This requires that the database has snapshot isolation enabled before you start:
ALTER DATABASE <dbname> SET ALLOW_SNAPSHOT_ISOLATION ON
Then in connection B
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
select * from Personel
There are some performance considerations with snapshot isolation, because it duplicates the rows read into tempdb.
Documentation reference
SNAPSHOT (aka. row versioning).
Under snapshot isolation the connection B will see the data as-it-was when ti started the transaction in connection B (even if you did not start an explicit transaction, there is an implicit transaction started by the SELECT statement). See Understanding Row Versioning-Based Isolation Levels:
Read operations performed by a snapshot transaction retrieve the last
version of each row that had been committed at the time the snapshot
transaction started.
SNAPSHOT support must be explictly enabled in teh database:
ALTER DATABASE <DatabaseName> SET ALLOW_SNAPSHOT_ISOLATION ON;
Related
I would like to force MS SQL Server to use SNAPSHOT isolation level, but in the same transaction, I have to run DML and DDL operation. As I know we can't run DDL (most of DDL operation) in a transaction with SNAPSHOT isolation level.
In article https://learn.microsoft.com/en-us/sql/t-sql/statements/set-transaction-isolation-level-transact-sql?view=sql-server-2017 we can read
If a transaction starts in the SNAPSHOT isolation level, you can change it to another isolation level and then back to SNAPSHOT. A transaction starts the first time it accesses data.
So my idea is to run a transaction with SNAPSHOT isolation level do DML and then switch transaction to READ COMMITTED isolation level and here is a problem.
/* the problematic part */
EXEC print_curr_il 'on start'
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
EXEC print_curr_il 'after setting il to snapshot'
BEGIN TRAN
EXEC print_curr_il 'before inserting'
INSERT INTO tbl2 VALUES ('some value')
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
EXEC print_curr_il 'after setting il to read committed'
ALTER TABLE tbl1 ADD val int
COMMIT
/* simple tables */
CREATE TABLE [dbo].[tbl1](
[text] [nchar](10) NULL
)
GO
CREATE TABLE [dbo].[tbl2](
[text] [nchar](10) NULL
)
GO
/* procedeure to print current isolation level */
CREATE PROCEDURE [dbo].[print_curr_il]
#desc varchar(50)
AS
BEGIN
Declare #il varchar(50);
SELECT #il =
CASE transaction_isolation_level
WHEN 0 THEN 'Unspecified'
WHEN 1 THEN 'ReadUncommitted'
WHEN 2 THEN 'ReadCommitted'
WHEN 3 THEN 'Repeatable'
WHEN 4 THEN 'Serializable'
WHEN 5 THEN 'Snapshot'
END
FROM sys.dm_exec_sessions where session_id = ##SPID;
print #desc + ' Isolation Level = ' + #il;
END
GO
As a result, I get
on start Isolation Level = ReadCommitted
after setting il to snapshot Isolation Level = Snapshot
before inserting Isolation Level = Snapshot
(1 row affected)
after setting il to read committed Isolation Level = ReadCommitted
Msg 3964, Level 16, State 1, Line 11
Transaction failed because this DDL statement is not allowed inside a snapshot isolation transaction. Since metadata is not versioned, a metadata change can lead to inconsistency if mixed within snapshot isolation.
So it looks like impossible, but I can't find the strict answer that it is impossible.
I suspect that SET TRANSACTION ISOLATION LEVEL ... on an open transaction is changing the behavior of locking, but not whole transaction type.
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.
I looked at all the isolation types.
But I could not find the mode I wanted.
It can be read by other transaction during the transaction.
But, it will not add update and delete data.
For example (pseudo code):
create table abc
(id uniqueidentifier primary key)
Create proc procMain
trans isolation level **??????**
insert abc (id) values (newid())
Waiting 10 minute
commit
Create proc procREAD
select * from abc
Create proc procAdd
insert abc (id) values (newid())
create proc procUpdate
update abc id = newid()
create proc procDelete
delete from abc
now;
exec procMain (abc table access read only and for other access: LOCKED)
(waiting...)
exec procRead (OK) (Readable)
exec procAdd (NO - never) (locked)
exec procUpdate (NO - never) (locked)
exec procDelete (NO - never) (locked)
Thanks...
Is there such an isolation level? (transaction isolation level?)
Sort of. If you set the READ COMMITTED SNAPSHOT database setting then READ COMMITTED sessions will not be blocked by in-flight transactions. But they will see the "last-known-good" version of the rows, ie the state before the current transaction started.
David
You're looking for READ UNCOMMITTED. Learn more here. Pay mind that this will lead to dirty reads.
I created a database called 'test_isolation' and created a table 'person' with data
name age
---- ---
test1 1
test2 2
test3 3
test4 4
test5 5
test6 6
Now the database is altered to allow snapshot isolation in session1
ALTER DATABASE test_isolation
SET ALLOW_SNAPSHOT_ISOLATION ON
GO
Now I create a transaction in session 2
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
GO
BEGIN TRAN
SELECT * FROM PERSON
GO
DELETE FROM PERSON WHERE name = 'test6'
GO
SELECT * FROM PERSON
GO
The results are as expected. (Note we haven't committed this transaction yet!)
Now I execute the following query in session 3
SELECT * FROM PERSON
The query in session 3 keeps on running infinitely which means the table is locked.
If I go back to session 2 and commit the transaction.. I'm able to run the query on session 3 and the results are as expected.
Transaction isolation level SNAPSHOT is not supposed lock the table right? Am I doing something wrong or my understanding of transaction SNAPSHOT isolation is wrong?
Please help..
You must explicitly declare SET TRANSACTION ISOLATION LEVEL SNAPSHOT in session three, otherwise session 3 will still operate as READ_COMMITTED and block on the update.
This option can also be set at the database level to replace READ_COMMITTED with SNAPSHOT.
ALTER DATABASE MyDatabase
SET READ_COMMITTED_SNAPSHOT ON
Should I run
ALTER DATABASE DbName SET ALLOW_SNAPSHOT_ISOLATION OFF
if snapshot transaction (TX) isolation (iso) is not temporarily used?
In other words,
why should it be enabled, in first place?
Why isn't it enabled by default?
What is the cost of having it enabled (but temporarily not used) in SQL Server?
--Update:
enabling of snapshot TX iso level on database does not change READ COMMITTED tx iso to be default.
You may check it by running:
use someDbName;
--( 1 )
alter database someDbName set allow_snapshot_isolation ON;
dbcc useroptions;
the last row shows that tx iso level of current session is (read committed).
So, enabling snapshot tx iso level without changing to it does not use it, etc
In order to use it one should issue
--( 2 )
SET TRANSACTION ISOLATION LEVEL SNAPSHOT
Update2:
I repeat the scripts from [1] but with SNAPSHOT enabled (but not switched on) but without enabling READ_COMMITTED_SNAPSHOT
--with enabling allow_snapshot_isolation
alter database snapshottest set allow_snapshot_isolation ON
-- but without enabling read_committed_snapshot
--alter database snapshottest set read_committed_snapshot ON
-- OR with OFF
alter database snapshottest set read_committed_snapshot OFF
go
There no results/rows from from executing
select * from sys.dm_tran_version_store
after executing INSERT, DELETE or UPDATE
Can you provide me with scripts illustrating that enabled SNAPSHOT tx iso level by ( 1 ) but not switched on by ( 2 ) produces any versions in tempdb and/or increase the size of data with 14 bytes per row?
Really I do not understand what is the point in versioning if it is enabled by ( 1 ) but not used (not set on by ( 2))?
[1]
Managing TempDB in SQL Server: TempDB Basics (Version Store: Simple Example)
Link
As soon as row versioning (aka. snapshot) is enabled in the database all writes have to be versioned. It doesn't matter under what isolation level the write occurred, since isolation levels always affect only reads. As soon the database row versioning is enabled, any insert/update/delete will:
increase the size of data with 14 bytes per row
possibly create an image of the data before the update in the version store (tempdb)
Again, it is completely irrelevant what isolation level is used. Note that row versioning occurs also if any of the following is true:
table has a trigger
MARS is enabled on the connection
Online index operation is running on the table
All this is explained in Row Versioning Resource Usage:
Each database row may use up to 14
bytes at the end of the row for row
versioning information. The row
versioning information contains the
transaction sequence number of the
transaction that committed the version
and the pointer to the versioned row.
These 14 bytes are added the first
time the row is modified, or when a
new row is inserted, under any
of these conditions:
READ_COMMITTED_SNAPSHOT or ALLOW_SNAPSHOT_ISOLATION options are
ON.
The table has a trigger.
Multiple Active Results Sets (MARS) is being used.
Online index build operations are currently running on the table.
...
Row versions must be stored for as
long as an active transaction needs to
access it. ... if it meets any of the
following conditions:
It uses row versioning-based isolation.
It uses triggers, MARS, or online index build operations.
It generates row versions.
Update
:setvar dbname testsnapshot
use master;
if db_id('$(dbname)') is not null
begin
alter database [$(dbname)] set single_user with rollback immediate;
drop database [$(dbname)];
end
go
create database [$(dbname)];
go
use [$(dbname)];
go
-- create a table before row versioning is enabled
--
create table t1 (i int not null);
go
insert into t1(i) values (1);
go
-- this check will show that the records do not contain a version number
--
select avg_record_size_in_bytes
from sys.dm_db_index_physical_stats (db_id(), object_id('t1'), NULL, NULL, 'DETAILED')
-- record size: 11 (lacks version info that is at least 14 bytes)
-- enable row versioning and and create an identical table
--
alter database [$(dbname)] set allow_snapshot_isolation on;
go
create table t2 (i int not null);
go
set transaction isolation level read committed;
go
insert into t2(i) values (1);
go
-- This check shows that the rows in t2 have version number
--
select avg_record_size_in_bytes
from sys.dm_db_index_physical_stats (db_id(), object_id('t2'), NULL, NULL, 'DETAILED')
-- record size: 25 (11+14)
-- this update will show that the version store has records
-- even though the isolation level is read commited
--
begin transaction;
update t1
set i += 1;
select * from sys.dm_tran_version_store;
commit;
go
-- And if we check again the row size of t1, its rows now have a version number
select avg_record_size_in_bytes
from sys.dm_db_index_physical_stats (db_id(), object_id('t1'), NULL, NULL, 'DETAILED')
-- record size: 25
By default, you have snapshot isolation OFF, If you turn it ON, SQL will maintain snapshots of data for running transactions.
Example: On connection 1, you are running big select. On connection 2, you update some of the records that are going to be returned by first select.
In snapshot isolation ON, SQL will make a temporary copy of the data, affected by update, so SELECT will return original data.
Any additional data manipulation will affect performance. That's why this setting is OFF by default.