Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: September 9, 2025
In this tutorial, we’ll study the locks in SQL Server 2022. We’ll use our University schema for queries.
A database uses locks to maintain data integrity and consistency in concurrent transactions. Locks enforce the ACID principle (Atomicity, Consistency, Isolation, and Durability) and avoid simultaneous conflicting operations.
SQL Server sets isolation levels to uphold integrity while processing concurrent transactions. An isolation level controls the degree to which one transaction is shielded from resource (data) modifications by other transactions. Those levels are implemented via various lock types:
SQL Server has two concurrency control types:
In pessimistic locking, we immediately lock to prevent other transactions from modifying the data. In contrast, we reduce lock contention in optimistic concurrency by freezing selected rows and isolating that DB snapshot.
We use a Shared lock (S) to allow concurrent read access without modification.
Here’s an example. In the first session, we set the isolation level to Repeatable Read to hold the lock till commit or rollback:
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
Then, we run a SELECT query with a shared lock to read a course:
BEGIN TRAN;
SELECT id, name, credits
FROM Course
WHERE id='CS111';
Here’s the output:
id name credits
---------- ----------------------------------- --------
CS111 Introduction to Operating Systems 7
We can check the shared lock in our first session is at the key level:
SELECT request_session_id, resource_type, request_mode
FROM sys.dm_tran_locks
WHERE request_session_id = @@SPID
AND resource_type='KEY';
request_session_id resource_type request_mode
------------------ --------------- -------------
101 KEY S
In our second session, we fetch the same course data using the same query, without blocking.
Now, we try to update the course credits. This time, we get stuck in a wait loop:
UPDATE Course
SET credits = 8
WHERE id='CS111';
Let’s open a third session and check the sessions that are in a suspended state:
SELECT
session_id,
blocking_session_id,
command,
status
FROM sys.dm_exec_requests
CROSS APPLY sys.dm_exec_sql_text(sql_handle)
WHERE blocking_session_id <> 0;
Our session 2 (id=101) is blocked by session 1 (id=100):
session_id blocking_session_id command status
---------- ------------------- -------- ----------
100 101 UPDATE suspended
We get unblocked by explicitly committing or rolling back the ongoing transaction that acquired the lock.
We use an exclusive lock (X) to bar other transactions from accessing or modifying the locked data. We do this to preserve data integrity.
Continuing the same example, in our first session, let’s set the isolation level to Repeatable Read and update the credits:
BEGIN TRAN;
UPDATE Course
SET credits = 8
WHERE id='CS111';
Here are the locks held:
request_session_id resource_type request_mode
------------------ --------------- -------------
101 DATABASE S
101 PAGE IU
101 KEY U
101 OBJECT IX
In our case of an UPDATE query on a single row, SQL Server acquires an Intent Exclusive (IX) lock at the object (table) level. This indicates our intention to place exclusive locks at the row (key) level.
Why don’t we see an X lock right away? Well, SQL Server follows a hierarchical locking structure:
If SQL Server placed an exclusive lock at the table level, no other session could access any different (even unrelated) row within that table. That would severely limit concurrency. Hence, the SQL Server uses intent locks at the table level to optimize performance. It holds an exclusive lock only at the precise row (key) being modified.
We’ll be blocked in our second session if we try to SELECT that row:
BEGIN TRAN;
SELECT id, name, credits
FROM Course
WHERE id='CS111';
We unblock our second session once we roll back the transaction from our first session.
An update lock (U) is a combination of a shared and an exclusive lock. It allows reads but not write operations.
For this case, we take an update lock using UPDLOCK and run our SELECT query with it:
BEGIN TRAN;
SELECT id, name, credits
FROM Course
WITH (UPDLOCK)
WHERE id='CS111';
Next, let’s check the lock type:
SELECT request_session_id, resource_type, request_mode
FROM sys.dm_tran_locks
WHERE request_session_id = @@SPID;
We got a U lock on the key:
request_session_id resource_type request_mode
------------------ --------------- -------------
54 DATABASE S
54 PAGE IU
54 KEY U
54 OBJECT IX
We can run the SELECT query to read this row in other transactions without blocking. However, we’ll be blocked in an update operation in another transaction unless we commit or roll back the first session.
In SQL Server, we place an intent lock at a higher-level resource (such as a table or page) to tell the SQL Server that we might request locks at a lower granularity (such as rows). This lock helps the SQL Server manage concurrent resources efficiently.
How? It uses lock intent requests to decide lock allocation.
We have three types of intent locks:
IS registers our intent to place a shared lock on lower-level resources (e.g, rows). With an IX lock, we tell SQL Server that we’ll shortly place an exclusive lock on a lower-level resource. IX is compatible with IS but not shared or exclusive schema locks.
Finally, SIX is a hybrid of shared and exclusive locks. It operates at a higher-level resource, such as a table. We use SIX to show the intent to place an exclusive lock at a lower level (e.g., rows). The objective is to prevent other transactions from placing exclusive locks on the resource while allowing shared access.
We use TABLOCKX and HOLDLOCK constructs to tell SQL Server we want a SIX lock: TABLOCKX stands for Table Exclusive Lock, and HOLDLOCK means Serializable isolation level. Serializable isolation internally uses key-range locks to prevent phantom reads. While running a transaction where we execute two identical queries (with some time lag), if we find the result of the second query different from the first, we call this a phantom read.
Under the Serializable isolation, SQL Server places key-range or table-level locks so that no new rows are inserted or deleted matching the query predicate. This ensures full consistency by blocking insertions or deletions in the queried range.
Let’s take a SIX lock over Course in our first session:
SELECT id, name, credits
FROM Course
WITH (TABLOCKX, HOLDLOCK)
WHERE id='CS111';
Then, run our UPDATE query. We can verify the SIX lock is held (Shared at the schema level and exclusive at the object (row) level):
request_session_id resource_type request_mode
------------------ -------------- --------------
52 DATABASE S
52 OBJECT X
In our second session, our SELECT query will be blocked due to the SIX lock from the first session.
SQL Server provides schema locks for schema modification or consistency checks. It has two subtypes:
Sch-M allows exclusive access to modify the database schema. We acquire it when we change the database’s structure, such as with ALTER TABLE, CREATE INDEX, and DROP TABLE.
On the other side, Sch-S locks ensure that the schema of a database object (like a table or index) isn’t altered while a previous session holds a Sch-S lock. SQL Server uses this lock during query compilation for SELECT queries.
SQL Server exposes a bulk update (BU) lock that we can use to perform bulk operations and improve performance.
A BU lock minimizes locking overhead, thereby improving performance. It allows multiple concurrent bulk insertions, shielding the data from conflicting modifications by other transactions.
A widespread use case of this lock is populating a table from a CSV file containing millions of entries.
When we set our transaction isolation level to Serializable, SQL Server gives us a special lock called a Key-Range lock (KR).
It prevents phantom reads. KR locks cover a range of keys that includes existing as well as potential new rows created by other transactions that can satisfy the query condition.
Here’s a tabular representation of important locks in SQL Server:
| Lock | Usecase | Compatability | Granularity | Performance |
|---|---|---|---|---|
| Shared (S) | Allows concurrent read-only access | S, IS | Row, Page, Table | Low |
| Exclusive (X) | Prevents concurrent access or modification | None | Row, Page, Table | High |
| Update (U) | Prevents common deadlocks by switching from S to X lock | S, IS | Row, Page | Medium |
| Shared Intent Exclusive (SIX) | Shared lock at high level with intent exclusive at lower levels | IS | Table | High |
| Schema Modification (Sch-M) | Exclusive schema change | None | Table | Very High |
| Bulk Update | Optimizes performance during bulk data loads | S, IS | Table | High |
| Key-range (KR) | Prevents phantom reads under Serializable isolation | None | Key ranges | High |
We should use a Shared lock to allow concurrent read-only access via SELECT queries. If we update a range of data and want no concurrent access or modification, we must use an exclusive lock. When making exclusive schema changes, we need a schema modification lock.
In this article, we covered lock types in SQL Server 2022.
We need SQL Server locks to maintain transactional integrity and balance concurrency and performance. Along with locks, we must set proper isolation levels to prevent performance bottlenecks and deadlocks. We should use locks sparingly with the lowest necessary isolation level and constantly monitor performance.