Understanding Transaction Isolation Levels in SQL Server

Joseph Chakola
4 min readApr 29, 2024

--

Solitude and isolation amidst desert vastness.

Introduction

Have you ever wondered what happens behind the scenes when you start a transaction in SQL Server? As a database developer, understanding how isolation levels work is crucial for writing correct and performant code. In this article, we’ll dive into the details of what happens when you begin an explicit transaction and run multiple statements before committing. We’ll explore whether the isolation level changes during the transaction and uncover some surprising insights. By the end, you’ll have a solid grasp of transaction isolation in SQL Server and be equipped to make informed decisions in your own projects. Let’s get started!

The Default Isolation Level: Read Committed

In SQL Server, the default isolation level is Read Committed. This means that by default, queries only read data that has been committed by other transactions. It prevents dirty reads, where a transaction reads uncommitted changes made by another transaction.

Under Read Committed, locks are taken at the statement level. Each individual SQL statement acquires the necessary locks, performs its work, and releases the locks when done. This allows for a good balance between concurrency and data consistency.

Here’s an example of a simple query running under Read Committed:

SELECT * FROM Customers WHERE CustomerID = 1;

This query will acquire shared (read) locks on the rows it needs from the Customers table, read the committed data, and release the locks immediately after the statement completes.

Beginning an Explicit Transaction

Things get more interesting when we start an explicit transaction using the BEGIN TRANSACTION statement. This marks the beginning of a transaction block, where multiple statements can be grouped together atomically.

BEGIN TRANSACTION;

UPDATE Orders SET Status = 'Shipped' WHERE OrderID = 10;
UPDATE Products SET QuantityInStock = QuantityInStock - 5 WHERE ProductID = 4;

COMMIT;

In this example, we start a transaction, perform two update statements, and then commit the transaction. The updates to the Orders and Products tables will either both succeed or both be rolled back, maintaining data integrity.

Isolation Level During Explicit Transactions

Now, the key question is: does the isolation level change when we’re inside an explicit transaction? The answer is no — the isolation level remains the same throughout the transaction, regardless of how many statements are executed.

If we began the transaction under Read Committed isolation, all queries within that transaction will continue to operate under Read Committed rules. The same locks will be taken and held for each individual statement until it completes.

However, there is one important nuance to be aware of. While the isolation level itself doesn’t change, the duration of the locks does differ compared to autocommit mode. Inside an explicit transaction, the locks acquired by each statement are held until the transaction commits or rolls back. This is necessary to maintain isolation between transactions.

Let’s revisit our earlier example to see this in action:

BEGIN TRANSACTION;

-- Shared locks acquired and held
SELECT * FROM Customers WHERE CustomerID = 1;

-- Exclusive locks acquired and held
UPDATE Orders SET Status = 'Shipped' WHERE OrderID = 10;

-- Locks released
COMMIT;

In this case, the shared locks acquired by the SELECT statement will be held for the duration of the transaction, until the COMMIT statement is reached. This prevents other transactions from modifying the rows being read by our transaction, ensuring a consistent view of the data.

Modifying Isolation Levels

It’s worth noting that we can change the isolation level explicitly for a specific transaction using the SET TRANSACTION ISOLATION LEVEL statement. This allows us to override the default Read Committed behavior and use a different isolation level like Serializable or Snapshot.

For example:

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
BEGIN TRANSACTION;

-- Queries here will use Serializable isolation
COMMIT;

By setting the isolation level to Serializable, we’re instructing SQL Server to acquire range locks and prevent phantom reads in addition to the usual protections provided by Read Committed. This can be useful in scenarios where strict data consistency is required, but it comes at the cost of reduced concurrency.

Conclusion

We’ve explored the intricacies of transaction isolation in SQL Server, focusing on what happens when we use explicit transactions. We learned that the default isolation level, Read Committed, remains in effect throughout the transaction, but the duration of locks is extended until the transaction commits or rolls back.

Understanding these concepts is crucial for writing robust and efficient database code. By knowing how isolation levels work and the impact of explicit transactions, we can make informed decisions about locking, concurrency, and data consistency.

As next steps, I encourage you to experiment with different isolation levels in your own projects and observe how they affect locking behavior and query performance. The more hands-on experience you gain, the better equipped you’ll be to tackle complex transaction scenarios in the future.

I hope this article has demystified transaction isolation in SQL Server and given you a solid foundation to build upon. Feel free to reach out if you have any further questions — I’m always happy to help! Happy coding!

Link to learn more: Transaction Isolation Levels

--

--

Joseph Chakola
Joseph Chakola

Written by Joseph Chakola

I'm a SQL Server DBA with over 17 years under my belt. Excited to share my knowledge and experience using AI to tackle the toughest db challenges out there.

No responses yet