Fix Guides
How to Fix "Unable to Lock Row" in Salesforce
Step-by-step fix guide with AI-powered diagnosis from BuildForce.
"Unable to lock row" means two transactions tried to update the same record — or, more often, the same master-detail parent whose roll-up summary fields both children are updating — at nearly the same moment, and Salesforce's row-level locking rejected the second one rather than risk a lost update. It's contention, not data corruption. The real fix is reducing how many transactions fight over the same hot record, plus retry-with-backoff for the residual cases — not "fixing" the lock itself.
Why Salesforce Locks Rows at All
Row-level locking prevents two concurrent transactions from silently overwriting each other's changes (a lost update / dirty write). Master-detail children escalate the lock to the parent specifically because recalculating a roll-up summary field requires the parent record to be locked for the duration of that recalculation — so any burst of child DML under one parent inherently serializes against that parent's lock.
Common Causes
- Bulk API / Data Loader jobs inserting or updating many child records under the same master-detail parent in parallel batches.
- High-frequency integrations writing to the same record from multiple concurrent API calls instead of serializing writes.
- Singleton "counter" or settings records updated in real time by every unrelated transaction in the org.
- Long-running Apex holding a lock open longer than necessary — nested DML across a hierarchy, or a callout made before commit inside the same transaction.
- Sharing recalculation competing mid-transaction with a DML operation on the same records.
How to Fix It — Step by Step
- 1. Identify the hot record. The error and debug log usually include the record Id that failed to lock. Confirm whether it's a specific business record (a busy parent Account) or a shared 'singleton' config/counter record being hit by unrelated transactions.
- 2. Reduce concurrency at the source for bulk loads. For Bulk API or Data Loader jobs writing many children under the same master-detail parent, lower the batch size and prefer serial processing over parallel batches when they target the same parent hierarchy.
- 3. Serialize integration writes to the same record. If multiple integration calls can write to the same record concurrently, queue writes to that record through a single async worker instead of firing parallel API calls that all race for the same lock.
- 4. Eliminate singleton 'counter' contention. If a trigger or Flow updates a shared settings or counter record on every transaction, every unrelated transaction in the org is now competing for that one row. Move that aggregation to an async/scheduled batch instead of updating it in real time inline with every unrelated transaction.
- 5. Add retry-with-backoff for the residual cases. Catch DmlException, check e.getDmlType(0) == StatusCode.UNABLE_TO_LOCK_ROW, and requeue the retry via Queueable Apex (synchronous Apex can't sleep) rather than failing the whole transaction outright.
- 6. Use FOR UPDATE for deterministic lock ordering. Where a transaction touches multiple parent records, query them with SELECT Id FROM Parent WHERE Id IN :ids ORDER BY Id FOR UPDATE so every code path acquires locks in the same order — this is what actually prevents deadlocks between two code paths, not retries alone.
- 7. Reconsider whether the rollup needs to be real-time. If the parent's total doesn't need to be accurate to the second, move the aggregation to a nightly or hourly scheduled batch instead of a trigger-time roll-up summary field that locks the parent on every child write.
Example: Retry With Queueable
try {
update parentRecord;
} catch (DmlException e) {
if (e.getDmlType(0) == StatusCode.UNABLE_TO_LOCK_ROW) {
System.enqueueJob(new RetryParentUpdateQueueable(parentRecord.Id));
} else {
throw e;
}
}This Error Is a Symptom, Not the Root Cause
A record that's hitting lock contention today is usually a structural hotspot — a master-detail parent with unusually high child fan-out, or a singleton record every automation touches — not a one-off timing accident. Run BuildForce's free health check to see how your org's overall setup compares to similar companies, then connect your org so BuildForce can flag high-fan-out relationships and singleton-record contention before the next bulk load or traffic spike turns it into a failure again.
How BuildForce Prevents This
- Continuous health checks — identify high-fan-out master-detail relationships and singleton "counter" records before they become lock-contention hotspots.
- Deployment automation — validate bulk-load and batch-size behavior against real org data before it runs in production.
- AI consultant for record-locking debugging — describe the failure and get a concrete retry and lock-ordering strategy.
FAQ
"Unable to lock row" is the same error as UNABLE_TO_LOCK_ROW?
Yes — 'unable to lock row' is the human-readable message Salesforce shows in the UI, Data Loader, and generic API error responses; UNABLE_TO_LOCK_ROW is the same failure surfaced as an Apex DmlException status code (checked via e.getDmlType(0)). Same underlying cause, different surface depending on where you hit it.
Why does this happen more with master-detail than lookup relationships?
Because a master-detail child update needs to recalculate the parent's roll-up summary fields, which requires locking the parent record for the duration of that recalculation. Every child insert/update under the same parent competes for that same parent-record lock — so a burst of child DML against one parent is the single most common trigger. Plain lookup relationships don't force this parent lock.
Can I just retry the whole transaction and move on?
For a single, low-frequency failure, yes — a simple retry often succeeds because the other transaction has released its lock by then. But if this is happening repeatedly, retrying without reducing the underlying contention just delays the same failure; you also need to reduce how many transactions are fighting over the same record (smaller batches, serialized writes, less real-time rollup logic) or the retries will keep failing under load too.
Does Data Loader batch size affect this?
Yes. Larger batches processed in parallel against child records under the same master-detail parent multiply how many transactions are simultaneously trying to lock that parent. Lowering batch size, or switching a load to serial mode instead of parallel Bulk API batches, is often the single highest-leverage fix for a Data Loader job that's hitting this error.
Can BuildForce find which record is a lock-contention hotspot?
BuildForce's health checks look for high-fan-out relationships — master-detail parents with disproportionately high child DML volume, and singleton 'counter' or settings records updated from many unrelated transactions — the structural patterns that produce lock contention before it starts showing up as failed transactions.