Transactions, isolation levels, RCSI and SNAPSHOT
Nested transactions that are not nested, savepoints, the locking isolation levels, and what read committed snapshot and SNAPSHOT isolation change.
After this lesson you can
- Predict @@TRANCOUNT and what is committed after nested BEGIN/COMMIT/ROLLBACK
- Undo part of a transaction with a savepoint
- Compare the isolation levels by the anomalies they allow and the locks they take
- Explain what RCSI and SNAPSHOT change for readers and writers
Nested transactions are a counter
SQL Server does not really nest transactions. BEGIN TRAN increments
@@TRANCOUNT; COMMIT decrements it and only commits when it reaches
zero; ROLLBACK (without a savepoint name) rolls back everything and
sets it to zero. An inner COMMIT therefore saves nothing on its own.
To undo part of a transaction, use a savepoint:
SAVE TRANSACTION before_line; and later
ROLLBACK TRANSACTION before_line;. That undoes the work since the
savepoint and leaves the transaction open.
CREATE TABLE t (id INT PRIMARY KEY, note NVARCHAR(20) NOT NULL);DECLARE @log TABLE (step INT, what NVARCHAR(40), trancount INT); BEGIN TRAN; INSERT INTO @log VALUES (1, N'outer BEGIN', @@TRANCOUNT); INSERT INTO t VALUES (1, N'outer'); BEGIN TRAN; INSERT INTO @log VALUES (2, N'inner BEGIN', @@TRANCOUNT); INSERT INTO t VALUES (2, N'inner'); COMMIT; INSERT INTO @log VALUES (3, N'inner COMMIT', @@TRANCOUNT);ROLLBACK;INSERT INTO @log VALUES (4, N'outer ROLLBACK', @@TRANCOUNT); BEGIN TRAN; INSERT INTO t VALUES (3, N'kept'); SAVE TRANSACTION before_risky; INSERT INTO t VALUES (4, N'undone'); ROLLBACK TRANSACTION before_risky; INSERT INTO @log VALUES (5, N'rollback to savepoint', @@TRANCOUNT);COMMIT; SELECT step, what, trancount, (SELECT STRING_AGG(CAST(id AS VARCHAR(5)), ',') WITHIN GROUP (ORDER BY id) FROM t) AS ids_in_table_at_endFROM @logORDER BY step;The locking isolation levels
| Level | Dirty read | Non-repeatable read | Phantom |
|---|---|---|---|
READ UNCOMMITTED (NOLOCK) | possible | possible | possible |
READ COMMITTED (default) | no | possible | possible |
REPEATABLE READ | no | no | possible |
SERIALIZABLE | no | no | no |
In the locking implementation, writers always take exclusive locks held
to the end of the transaction. What changes is the readers: under
READ COMMITTED a shared lock is released as soon as the row is read;
REPEATABLE READ keeps shared locks until the end; SERIALIZABLE
also locks the it read, so nobody can insert a phantom
into them. Each step up means more blocking.
CREATE TABLE t (id INT PRIMARY KEY, v INT NOT NULL);INSERT INTO t VALUES (1, 10);DECLARE @log TABLE (outcome NVARCHAR(40));SET TRANSACTION ISOLATION LEVEL SNAPSHOT;BEGIN TRY BEGIN TRAN; SELECT v FROM t WHERE id = 1; COMMIT; INSERT INTO @log VALUES (N'read succeeded');END TRYBEGIN CATCH IF @@TRANCOUNT > 0 ROLLBACK; INSERT INTO @log VALUES (CONCAT(N'error ', ERROR_NUMBER()));END CATCH;SELECT outcome FROM @log;ALTER DATABASE CURRENT SET ALLOW_SNAPSHOT_ISOLATION ON;GOALTER DATABASE CURRENT SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;GOCREATE TABLE t (id INT PRIMARY KEY, v INT NOT NULL);INSERT INTO t VALUES (1, 10);DECLARE @v INT;SET TRANSACTION ISOLATION LEVEL SNAPSHOT;BEGIN TRAN; SELECT @v = v FROM t WHERE id = 1;COMMIT; SELECT @v AS value_read, d.snapshot_isolation_state_desc, d.is_read_committed_snapshot_onFROM sys.databases AS dWHERE d.database_id = DB_ID();Common mistakes
- Expecting an inner
COMMITto persist work. The first example rolls back both rows with one outerROLLBACK. NOLOCKeverywhere to cure blocking, then explaining duplicated rows in a report.- Asking for
SNAPSHOTwithout enabling it. The second example fails with error 3952. - Not retrying on error 3960 under
SNAPSHOT.
What interviewers ask
- What does RCSI change, and what does it cost? Readers see the last committed version per statement without shared locks; costs version store space (tempdb, or the database itself with ADR) and a little per-row overhead.
- RCSI versus SNAPSHOT isolation? Statement-level versus transaction-level consistency; SNAPSHOT is opt-in per session and can fail with update conflicts.
- What happens to
@@TRANCOUNTwith nested transactions? It counts; only the outermostCOMMITcommits, and anyROLLBACKundoes it all.
Exercise · Pro
Accept whole orders or nothing, with savepoints
Exercises are free in the first 3 lessons of every course. From here on they are part of Pro and Sprint; the lesson and its runnable examples stay open.
Upgrade