Errors 1205, 1222 and -2 look alike in a log and mean very different things. How to tell a deadlock from a lock timeout from a client give-up, and find the contention.
Key takeaways
- 1205 arrives with an automatic rollback. The engine has already undone your transaction.
- 1222 is `Lock request time out period exceeded` - something else held a lock longer than the configured patience.
- -2 is a client-side `CommandTimeout` expiry. The query may still be running on the server after the application walked away.
- Raising the timeout extends the blocking rather than removing it. It converts a fast error into a long hang.
- The deadlock graph already exists on your server. The `system_health` session collects it by default.
Error 1205 is a deadlock: the engine detected a cycle of locks and picked your transaction as the victim. Error 1222 is a lock wait that expired. Error -2 comes from the client, not the server, and means your application gave up. All three surface in a log as "a database error", and only one of them is actually a cycle.
Three failures that look identical in an application log
What separates them is who gave up first: the engine, the lock manager, or your own code.
| Number | Origin | What happened | Rollback |
|---|---|---|---|
| 1205 | SQL Server, symbolic name LK_VICTIM | A lock cycle; the engine chose a victim | Yes, automatic |
| 1222 | SQL Server, symbolic name LK_TIMEOUT | The lock wait exceeded LOCK_TIMEOUT | Not necessarily |
| -2 | SqlClient, on the client side | CommandTimeout elapsed | No, and the query may continue |
| none | Ordinary blocking | An operation waits and eventually succeeds | No |
The exact text of 1205 is Transaction (Process ID %d) was deadlocked on %.*ls resources with another process and has been chosen as the deadlock victim. Rerun the transaction. The text of 1222 is far shorter: Lock request time out period exceeded.
Why does raising the timeout make things worse?
Because it does not touch the cause. A -2 is the symptom of an operation taking longer than it was allotted, whether from blocking or from a poor execution plan. Going from 30 seconds to 300 changes exactly one thing: how long the user stares at the screen before receiving the same outcome.
- A waiting connection occupies a slot in the connection pool. When the pool is exhausted, healthy requests start failing too.
- A waiting transaction still holds the locks it already acquired, so a longer block builds a longer queue behind it.
SET LOCK_TIMEOUTdefaults to -1, meaning wait indefinitely. A value of 0 means do not wait at all.- In .NET,
CommandTimeoutdefaults to 30 seconds and a value of 0 removes the limit entirely. Never set 0 on a code path a user is waiting on.
The practical rule: a short timeout is a measuring instrument. It is what tells you contention exists. Raising it switches the instrument off.
Where the deadlock graph already lives
SQL Server runs an Extended Events session called system_health by default, and it collects xml_deadlock_report events. If a deadlock happened in the last few days, its graph is very likely already stored on the instance - you do not need to enable new tracing and wait for a recurrence.
The graph has three parts: victim-list with the identifier of the chosen victim, process-list with every process involved, and resource-list with the resources they contended over. Saving the XML with an .xdl extension and opening it in SSMS renders it as a diagram.
Be aware that the event file target is bounded in size and file count, so deep history is not retained indefinitely. If your deadlock happens once a fortnight, export the graph the moment it is captured.
How do you find contention while it is happening?
A recurring fault is solved far faster when you catch it live:
- Query
sys.dm_exec_requestsfor rows whereblocking_session_idis not zero. That identifies who is blocking whom. - Cross-reference
sys.dm_os_waiting_tasksto see precisely what the wait is on. - Query
sys.dm_tran_locksfiltered torequest_statusofWAIT. Those are the lock requests that have not been granted. - Read
resource_typeandrequest_modeon the same row: together they tell you whether a single row, a page or an entire table is locked. - Find the blocking session and check what it is running and when its transaction opened.
- Record the session ids and the statements before you release anything. After a
KILL, that evidence is gone.
What actually fixes contention
Microsoft's own documentation states that deadlocks are in most cases application-related and require code changes. In order of payoff:
- Access tables in the same order in every transaction. If everyone touches Table1 then Table2, blocking may still occur but a cycle cannot form. This is Microsoft's first recommendation and the cheapest to implement.
- Shorter transactions. Do not open a transaction before calling an external service, and never hold one open while a user is thinking.
- A suitable index. Scanning a whole table locks far more than it needs to. An index that narrows the scan directly narrows the locked surface.
- Readers that do not block writers.
ALTER DATABASE ... SET READ_COMMITTED_SNAPSHOT ONmoves READ COMMITTED transactions onto row versions instead of locks. Note that enabling it requires no active connections to the database other than the one running the statement, so it belongs in a maintenance window. - A controlled retry, on 1205 only. A deadlock victim has been rolled back, so a rerun usually succeeds. Add a short pause before retrying and cap the number of attempts.
When is SET DEADLOCK_PRIORITY the right tool?
SET DEADLOCK_PRIORITY does not prevent deadlocks. It only decides who gets terminated when one occurs. The values are LOW, NORMAL and HIGH, mapping to -5, 0 and 5 on a range from -10 to 10. NORMAL is the default.
When both sessions carry the same priority, SQL Server chooses whichever is cheaper to roll back, measured by the number of log bytes written to that point. That figure appears in the deadlock graph as Log Used.
The one legitimate use: a background job that can be rerun quietly is given LOW so it never kills a real user's operation. Setting HIGH so "my code stops failing" simply moves the pain onto somebody else.
What to log so the next occurrence is diagnosable
- The exact error number, not just "database error".
- A timestamp with seconds, so you can line it up against the deadlock graph.
- The business operation and its parameters, not only the SQL text.
- The actual wait duration before the error was raised.
- The session id, where the client can see it.
Without the error number and a timestamp, every after-the-fact diagnosis is a guess. It is the same conclusion you reach with any integration fault that was swallowed without a log entry.
Related: common Priority API errors, a recovery plan for a corrupted MDB file, what a database is.
Sources
Frequently asked questions
Should I retry automatically after a 1205?
Yes, and Microsoft recommends it. The victim has been fully rolled back, so a rerun usually succeeds. Add a short pause before the attempt so the other transaction can finish and release its locks, and cap the retries rather than looping forever.
What is the difference between blocking and a deadlock?
Blocking is a wait that resolves itself once the other side releases. A deadlock is a cycle that will never resolve without intervention, which is why SQL Server detects it and terminates one side. Blocking that lasts long enough looks like a timeout, but it is not a deadlock.
Why does the query keep running after a -2 error?
Because -2 is a client-side give-up, not a server-side one. The application stops waiting, but the transaction on the server may remain open and keep holding locks. That is why raising the timeout as a fix sometimes produces a second wave of blocking.
Does READ_COMMITTED_SNAPSHOT solve everything?
No. It removes blocking between readers and writers, but it does not prevent a deadlock between two writers. It also adds load to tempdb and needs a maintenance window, because no other active connections may exist while it is being enabled.
Keep reading
Related service
Access & Excel Rescue
Stabilise the file the business runs on, then fix it or move it to the web.
About the author
Yehonatan Saadia
Freelance automation, web & MVP developer
I'm Yehonatan Saadia, a senior developer who builds business automation, custom websites, and MVPs for small and mid-sized companies across the US, Europe, and Israel. These guides come from real client work, not theory.
Work with meHave a project like this?
Tell me what you're trying to automate or build. I reply within 24 business hours with a few targeted questions, then we walk through it on a free 30-minute call, with no commitment. You come away with a scope, a timeline and a fixed price - or a straight answer that it isn't worth building.
