SQL Server error 1222
Lock request time out period exceeded. (SQL Server error 1222)
Your statement waited longer than its SET LOCK_TIMEOUT for a lock another session holds, so SQL Server cancelled that one statement. Your transaction is still open. Find the blocker with sys.dm_exec_requests and commit, roll back or end it.
Lock request time out period exceeded.
Tested on SQL Server 2022 (16.0.4295.3); also 2019 (15.0.4490.9) and 2025 (17.0.5005.3) · Updated 9 October 2026
What it means
Another session holds a lock your statement needs (a row it changed, a table it altered), and your
statement waited as long as SET LOCK_TIMEOUT allows, then gave up with error 1222.
By default SQL Server has no lock timeout: @@LOCK_TIMEOUT is -1 and a blocked statement waits
forever, or until the client’s own command timeout cancels it (30 seconds in many .NET apps, which
shows up as a client timeout, not 1222). So if you see 1222, something set a limit: your code, a
tool, or a library. SET LOCK_TIMEOUT 0 means don’t wait at all.
What 1222 does and doesn’t undo:
- Only the statement that waited is cancelled. The batch carries on with the next statement.
- Your transaction stays open, with everything it did before. If your code doesn’t notice the error, it can go on and commit a transaction that’s missing a step.
- With
SET XACT_ABORT ON, the error rolls back the whole transaction instead.
Common causes
- A transaction left open somewhere: a session that ran
BEGIN TRANSACTION(or turned off autocommit) and never committed, often an idle query window or an app that forgot to commit. - A long-running write, such as a big
UPDATE,DELETEor index rebuild, holding locks on rows or the whole table. - Schema changes.
ALTER TABLEand some index operations need a schema-modification lock that waits for every reader, and blocks everyone queued behind it. - Readers blocked by writers under the default
READ COMMITTEDlevel, which takes shared locks and waits for uncommitted changes. - A short timeout set by a tool or framework, so ordinary waits fail.
How to fix it
Find the blocker
While it’s happening, from another session:
SELECT r.session_id, r.blocking_session_id, r.wait_type, r.wait_time, r.wait_resource, t.text
FROM sys.dm_exec_requests AS r
CROSS APPLY sys.dm_exec_sql_text(r.sql_handle) AS t
WHERE r.blocking_session_id <> 0;
blocking_session_id is the session holding the lock. If that session isn’t running anything, it
won’t be in sys.dm_exec_requests; look it up in sys.dm_exec_sessions instead:
SELECT s.session_id, s.login_name, s.host_name, s.program_name, s.status,
s.open_transaction_count, s.last_request_end_time
FROM sys.dm_exec_sessions AS s
WHERE s.session_id = <blocking session id>;
sleeping with open_transaction_count above 0 is the classic forgotten transaction. EXEC sp_who2
gives a quicker overview: its BlkBy column shows the blocker’s session ID.
Without the VIEW SERVER STATE permission (or VIEW SERVER PERFORMANCE STATE on 2022 and later),
both only show your own session, so ask someone who has it.
End the blocking transaction
Best is to have its owner commit or roll back. If you can’t, end the session; its open transaction is rolled back, which can take as long as the work it undoes:
KILL <session id>;
KILL needs ALTER ANY CONNECTION. Check what the session is first: killing a session in the
middle of a big batch job throws away its work.
Handle 1222 in your code
If you set a lock timeout, treat 1222 as a failed step and roll back, or turn on XACT_ABORT so the
server does it for you:
SET XACT_ABORT ON;
SET LOCK_TIMEOUT 5000; -- milliseconds
BEGIN TRY
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance + 1 WHERE id = 2;
UPDATE accounts SET balance = balance + 1 WHERE id = 1;
COMMIT;
END TRY
BEGIN CATCH
IF XACT_STATE() <> 0 ROLLBACK;
THROW;
END CATCH;
Then retry the whole transaction later, or report that the data is busy.
Don’t make readers wait
For reads that can use the last committed data, row versioning avoids the wait entirely. If the
database allows snapshot isolation (ALLOW_SNAPSHOT_ISOLATION ON), a session can ask for it:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
SELECT id, balance FROM accounts WHERE id = 1;
Or turn on READ_COMMITTED_SNAPSHOT for the database, so every READ COMMITTED read does this
without code changes (it needs a moment with no other sessions, and testing; see
deadlock victim). The
READPAST table hint skips locked rows instead, which suits queue tables. NOLOCK also stops the
wait, but it reads uncommitted changes that may be rolled back, so avoid it for anything that
matters.
Run schema changes and big writes carefully
Run ALTER TABLE and index rebuilds when traffic is low, with a lock timeout on that session so
it gives up instead of queueing everyone behind it, and split large UPDATE and DELETE statements
into batches of a few thousand rows, committing between them.
Reproduce it
On SQL Server 2022 (RTM-CU27, 16.0.4295.3), two sqlcmd sessions a second apart on a table
seo_sqlerr_conn.accounts:
-- Session 1 (spid 58): holds a row lock for 10 seconds
BEGIN TRAN;
UPDATE seo_sqlerr_conn.accounts SET balance = balance - 10 WHERE id = 1;
WAITFOR DELAY '00:00:10';
ROLLBACK;
-- Session 2
SELECT @@LOCK_TIMEOUT AS default_lock_timeout;
SET LOCK_TIMEOUT 2000;
BEGIN TRAN;
UPDATE seo_sqlerr_conn.accounts SET balance = balance + 1 WHERE id = 2;
UPDATE seo_sqlerr_conn.accounts SET balance = balance + 1 WHERE id = 1;
SELECT @@TRANCOUNT AS trancount, XACT_STATE() AS xact_state;
SELECT id, balance FROM seo_sqlerr_conn.accounts WITH (READPAST);
ROLLBACK;
Session 2 printed:
default_lock_timeout
--------------------
-1
Msg 1222, Level 16, State 51, Server 7732422b7f56, Line 6
Lock request time out period exceeded.
The statement has been terminated.
trancount xact_state
--------- ----------
1 1
id balance
-- -------
2 101.00
The second update was cancelled after two seconds, but the transaction was still open and
committable, with the first update in it (101.00): a COMMIT there would have saved half the
transfer. READPAST skipped row 1, which session 1 had locked.
In an earlier run, while a SELECT of row 1 (session 62, with a 5-second timeout) was waiting on
session 58, the query above from a third session showed the blocker:
session_id blocking_session_id wait_type wait_time wait_resource text
---------- ------------------- --------- --------- ------------- ----
62 58 LCK_M_S 2008 KEY: 5:72057594049331200 (8194443284a0) (@1 tinyint)SELECT [id],[balance] FROM [seo_sqlerr_conn].[accounts] WHERE [id]=@1
and sp_who2 listed session 62 as SUSPENDED with 58 in BlkBy. Run as a login without
VIEW SERVER STATE, sys.dm_exec_requests and sp_who2 returned only that login’s own session.
The same SELECT with SET TRANSACTION ISOLATION LEVEL SNAPSHOT (the test database allows snapshot
isolation) returned at once, with the last committed balance, 100.00.
SQL Server 2019 (15.0.4490.9) and 2025 (17.0.5005.3) gave the same error, level and state.
In Inlet
Inlet’s Activity view finds the blocker while your statement waits: Sessions shows who blocks whom
and can end a session (KILL, which needs ALTER ANY CONNECTION), and Locks lists each session’s
locks per table and index, waits first, naming the session each wait is for. Activity reads the
server’s DMVs, so it needs VIEW SERVER STATE (VIEW SERVER PERFORMANCE STATE from SQL Server 2022,
VIEW DATABASE STATE on Azure SQL Database). On connections tagged production, Inlet opens
read-only and refuses writes before they’re sent, so a browsing session there doesn’t leave changes,
and their locks, open. When a statement fails with 1222, the error links to this page.
Related
- Transaction (Process ID …) was deadlocked on lock resources with another process and has been chosen as the deadlock victim
- The current transaction cannot be committed and cannot support operations that write to the log file. Roll back the transaction.
- canceling statement due to lock timeout
- ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
- canceling statement due to statement timeout
Sources
- learn.microsoft.com/en-us/sql/t-sql/statements/set-lock-timeout-transact-sql
- learn.microsoft.com/en-us/sql/relational-databases/system-dynamic-management-views/sys-dm-exec-requests-transact-sql
- learn.microsoft.com/en-us/sql/t-sql/statements/alter-database-transact-sql-set-options
- learn.microsoft.com/en-us/sql/t-sql/language-elements/kill-transact-sql
- learn.microsoft.com/en-us/dotnet/api/microsoft.data.sqlclient.sqlcommand.commandtimeout