SQL Server Error 1222 (Lock Request Timeout) or Oracle ORA-00060

SQL Query Timeout from Lock Contention Storm – Real Fixes

Database Errors Intermediate 👁 13 views 📅 Jun 18, 2026

When your query dies with a timeout because locks pile up like cars in a pileup. I'll show you three real-world causes and the fixes that actually work.

1. The Most Common Cause: Too Many Long-Running Transactions Holding Locks

This one's the biggest culprit. You've got a query that's doing a big update, maybe across millions of rows, and it's holding exclusive locks for way too long. Meanwhile, another query comes in trying to read or update part of that data and it sits there waiting until it hits the 30-second default timeout (SQL Server) or whatever you've set. I had a client last month whose entire print queue died because their inventory update was locking the order table for 5 minutes straight.

What actually happens

SQL Server uses row-level locks by default, but a big update will often escalate to page or table locks. When you have 3 or 4 concurrent sessions doing large updates, you get a lock queue. Each session waits on the previous one. The last query in line times out because it waited too long.

How to fix it – break the updates into smaller chunks

Don't update 500,000 rows in one transaction. Break it into batches of 1,000 or 5,000 rows. Here's a pattern I use for SQL Server:

DECLARE @BatchSize INT = 5000, @RowsAffected INT = 1

WHILE @RowsAffected > 0
BEGIN
    UPDATE TOP (@BatchSize) Orders
    SET Status = 'Processed'
    WHERE Status = 'Pending'
    AND ProcessedDate IS NULL
    
    SET @RowsAffected = @@ROWCOUNT
    
    -- Small pause to let other queries in
    WAITFOR DELAY '00:00:00.100'
END

This keeps locks tiny and gives other queries a chance to sneak in. In Oracle, use ROWNUM or FETCH FIRST. In PostgreSQL, use LIMIT within a loop. The key is the delay – even 100ms helps.

When this doesn't work: If you have hundreds of short queries all hitting the same hot spot (like a counter table), batch updates won't help. Then read on.

2. Second Most Common: Read Queries Blocking Writes (and Vice Versa)

This is the one people miss. You have a report query that runs for 5 minutes, reading millions of rows. While it's running, it's holding shared locks on every row it touches. Now a small update query comes in, wants to update one of those rows, and it's blocked. The update times out because it's waiting on the read. Your users think the system is broken, but really it's just the report hogging the table.

The real-world case

Had a client whose sales team couldn't close deals because the nightly data warehouse extract was running during the day and locking the live orders table. The extract was just a SELECT, but it was running at default isolation level (READ COMMITTED), which holds shared locks for the duration of the query.

Fix: Use snapshot isolation or NOLOCK (with caution)

SQL Server 2005 and later have READ COMMITTED SNAPSHOT (RCSI). Turn it on and reads won't block writes. Here's how:

ALTER DATABASE YourDB SET READ_COMMITTED_SNAPSHOT ON
ALTER DATABASE YourDB SET ALLOW_SNAPSHOT_ISOLATION ON

This gives you row versioning – every read gets a snapshot of the data as of when the query started. Writes still work normally. For Oracle, it's the default behavior (undo segments handle this). For PostgreSQL, set default_transaction_isolation = 'read committed' which already avoids read-write blocking in most cases.

If you can't change the database setting (maybe a legacy app), slap WITH (NOLOCK) on your report queries. But be warned – NOLOCK can give you dirty reads or missing rows. I've seen it cause duplicate invoice numbers because a report saw uncommitted data. Use it only for rough counts, never for anything financial.

Alternative fix: Schedule your heavy reads outside business hours. If you can't, snapshot isolation is your friend.

3. Third Most Common: Missing Index Causing Table Scans That Escalate Locks

This one's subtle. You have a query that should be fast – a simple update like UPDATE Orders SET Status='Shipped' WHERE OrderID=12345. But it's timing out. Why? Because the WHERE clause is on a column without an index. SQL Server has to scan the whole table. During that scan, it locks every row it touches. If another session is doing the same, they collide.

How I caught this once

A client's CRM was locking up every morning at 9:15 AM. I ran sp_who2 and saw a blocking chain with BLK = 37. Ran DBCC INPUTBUFFER to find the update query. The WHERE clause was using a non-indexed custom field. Added an index – the lock contention vanished.

Fix: Find and fix the missing index

Use the DMV sys.dm_db_index_usage_stats to see which indexes are missing. But the fastest way is to use the Database Engine Tuning Advisor or the Missing Index DMV:

SELECT 
    migs.avg_user_impact,
    migs.avg_total_user_cost,
    mid.statement AS TableName,
    mid.equality_columns,
    mid.inequality_columns,
    mid.included_columns
FROM sys.dm_db_missing_index_groups mig
INNER JOIN sys.dm_db_missing_index_group_stats migs 
    ON migs.group_handle = mig.index_group_handle
INNER JOIN sys.dm_db_missing_index_details mid 
    ON mig.index_handle = mid.index_handle
WHERE mid.database_id = DB_ID()
ORDER BY migs.avg_user_impact DESC

Look for queries with high impact (over 50%) that involve a table you're having contention on. Create that index. For SQL Server, include the clustered key or included columns to cover the SELECT part. For PostgreSQL, use pg_stat_user_tables and pg_stat_all_indexes to spot table scans.

One more thing – if you have a table with heavy updates but also heavy reads, consider using filtered indexes for the most common WHERE conditions. Index only the active range.

Quick-Reference Summary Table

Cause What You See Fix
Long transactions holding locks Update/delete on millions of rows locks entire table Batch updates (5000 rows with delay)
Reads blocking writes Report SELECT holds shared locks, small UPDATE times out Enable snapshot isolation or use NOLOCK
Missing index causing lock escalation Simple WHERE update on non-indexed column causes table scan Create covering index on WHERE column

I've used these exact fixes on dozens of production systems. Start with the most likely cause based on what you see in your blocking chains. Run sp_who2 or sys.dm_exec_requests to see what's actually waiting. Nine times out of ten, it's one of these three.

Was this solution helpful?