Introduction
Financial ledger tables are some of the most heavily accessed structures in enterprise systems. They store critical accounting events such as journal entries, postings, adjustments, settlements, accruals, and reversals. These tables often experience very high write concurrency, especially in:
ERP posting engines
High-volume billing systems
Payment gateways
Reconciliation services
End-of-day and batch processing jobs
As concurrency increases, SQL Server frequently faces lock contention, leading to:
Long-running transactions
Deadlocks
Slow inserts/updates
Users experiencing blocking
Increased TempDB usage
Timeouts in APIs or stored procedures
This article explains why ledger tables suffer from lock contention, and provides practical, implementable techniques to reduce blocking, improve throughput, and increase system stability.
Why Financial Ledger Tables Face High Lock Contention
1. Constant Writes
Ledger tables often record every financial event.
Even medium systems may generate:
50–200 records per user action
Thousands of rows per batch job
Millions of rows per day
Continuous writes cause frequent locks.
2. Wide Rows With Many Columns
Ledger rows often contain:
CompanyId
AccountId
Debit/Credit
Amount
Currency
ReferenceId
Audit columns
Wide rows increase the cost of locking and reduce throughput.
3. Heavy Use Of Aggregates
Financial reporting queries run concurrently with postings:
SELECT AccountId, SUM(Debit) - SUM(Credit)
FROM Ledger
WHERE AccountingPeriod = '2025-01'
GROUP BY AccountId;Such queries escalate to locking large ranges of rows.
4. Range Locks Due To Non-Optimized Indexing
If AccountId or PostingDate is not properly indexed, SQL Server uses:
Table scans
Key-range locks
Intent locks
These escalate quickly under concurrency.
5. Long Transactions
Posting operations often perform multiple validations and write across several tables.
Long transactions hold locks longer, increasing blocking.
Lock Contention Symptoms In Ledger Tables
Common Issues
Blocking chains
Deadlocks involving ledger rows
LCK_M_S, LCK_M_U, LCK_M_X waits
WRITELOG and PAGELATCH waits
API timeouts during accounting operations
Transaction log growth due to long-running transactions
Typical Indicators
SELECT * FROM sys.dm_tran_locks
WHERE resource_associated_entity_id = OBJECT_ID('Ledger');
SELECT * FROM sys.dm_os_waiting_tasks
WHERE wait_type LIKE 'LCK%';High frequency of these waits confirms contention.
Technique 1: Use Appropriate Isolation Levels
Default Behavior
Most systems run in READ COMMITTED, causing shared locks during reads.
Recommended
Enable READ_COMMITTED_SNAPSHOT (RCSI):
ALTER DATABASE FinanceDB SET READ_COMMITTED_SNAPSHOT ON;Benefits:
Readers do not block writers
Writers do not block readers
Ledger reporting queries become non-blocking
This single change can reduce contention by 70–90%.
When To Use Snapshot Isolation
If high consistency is needed for accounting operations:
ALTER DATABASE FinanceDB SET ALLOW_SNAPSHOT_ISOLATION ON;
Then in code:
SET TRANSACTION ISOLATION LEVEL SNAPSHOT;
Use it for:
Posting engines
Settlement jobs
Reconciliation processes
Technique 2: Narrow The Update Scope
Problem
Some ledger updates touch many rows at once:
UPDATE Ledger
SET IsReconciled = 1
WHERE AccountId = @Acct AND PostingDate = @Date;
This creates:
Key-range locks
Long blocking spans
Update lock escalations
Solution
Break the update into small batches:
WHILE 1=1
BEGIN
WITH cte AS (
SELECT TOP (500) LedgerId
FROM Ledger
WHERE AccountId = @Acct
AND PostingDate = @Date
AND IsReconciled = 0
)
UPDATE cte SET IsReconciled = 1;
IF @@ROWCOUNT < 500 BREAK;
END
Benefits:
Smaller locks
Faster release of resources
Lower deadlock probability

Join the conversation! Your thoughts help the community grow.