Fix Guides

How to Fix "Unable to Lock Row" in Salesforce

Step-by-step fix guide with AI-powered diagnosis from Wrenk.

"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.

The Error: "UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record"

This is the Apex DmlException form — thrown when a synchronous DML statement can't acquire the row lock it needs because another transaction already holds it. Check e.getDmlType(0) == StatusCode.UNABLE_TO_LOCK_ROW in the catch block to confirm this is the failure before deciding how to handle it.

The Error (UI / Data Loader): "Unable to Lock Row"

This is the same underlying lock-contention failure, surfaced outside Apex — in the Salesforce UI, Data Loader logs, and generic REST/SOAP API error responses. It carries the same message either way: another user or process was updating this data at nearly the same moment.

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 puts every one of those transactions in a race for the same parent-record lock.
  • High-frequency integrations — Multiple concurrent API calls writing to the same record instead of serializing those writes through a single path.
  • Singleton "counter" or settings records — A record updated in real time by every unrelated transaction in the org becomes a lock bottleneck for the whole org, not just one process.
  • 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 — widens the window for another transaction to collide with it.
  • Sharing recalculation — Salesforce's sharing recalculation can compete mid-transaction with a DML operation touching the same records.

How to Fix It — Step by Step

  1. 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. 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. 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. 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. 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. 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. 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;
    }
}

Prevention

  • Cap Data Loader / Bulk API batch size and prefer serial mode for any load writing many children under one master-detail parent, instead of discovering the ceiling from a failed production import.
  • Move singleton counters and settings updates off the synchronous path so no shared record is written inline by every unrelated transaction in the org.
  • Review new integrations for serialized writes to any record they update concurrently, before the integration goes live against production traffic.
  • Watch for master-detail parents with high child fan-out as your data model grows — a relationship that's fine at low volume can become a lock-contention hotspot once child DML volume increases.

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 Wrenk's free health check to find every instance of this pattern in your org, not just the record that just failed — then connect your org so Wrenk 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 Wrenk Prevents This

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 Wrenk find which record is a lock-contention hotspot?

Wrenk'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.