Not verified hands-on. This article was written from primary documentation rather than reproduced in an org, so it carries no tested-release badge. If you run it and something differs, say so in the comments and it gets tested and corrected.

In one sentence: sort your CSV by the parent ID before loading. Bulk API splits the file into chunks and processes them in parallel, and two chunks holding children of the same parent will both try to lock that parent. One wins, the other gets UNABLE_TO_LOCK_ROW. Sorting puts all the children of one parent next to each other, so they end up in the same chunk and get processed one after another instead of at the same time.

Why the parent gets locked at all

Inserting or updating a child record takes a lock on its parent. For a master-detail relationship this is guaranteed, because the parent owns the child’s sharing and the roll-up summaries have to be recalculated. Lookups take a lock in many cases too, and anything that forces a write to the parent, a roll-up field, a flow that updates the account, a sharing recalculation, makes it certain.

The classic cases are contacts and opportunities under an account, line items under an opportunity, and any custom child object under a custom parent. Salesforce holds the lock for the duration of the chunk’s transaction and releases it at commit.

What parallel processing does to your file

Bulk API 2.0 takes your uploaded file, splits it server side, and runs the pieces concurrently. The split follows file order: rows that are adjacent in the file end up in the same piece. That is the whole mechanism you are exploiting.

An unsorted file of 100,000 contacts spread randomly across 5,000 accounts will have children of the same account scattered through every chunk. Sorted by AccountId, each account’s contacts occupy a contiguous run, and a given account appears in one chunk or, at worst, at the boundary of two.

sort -t, -k3,3 contacts_unsorted.csv > contacts_sorted.csv

What sorting does not fix

Sorting solves collisions between chunks. It does nothing about:

  • Skew. If one account has 40,000 children, sorting puts them all together and they still cannot fit in a single chunk. That account spans several chunks and they contend with each other. This is account data skew and the fix is to restructure the data or load that account separately.
  • Ownership skew. Many records owned by one user causes sharing recalculation on that user’s row. Different parent, same problem.

Serial mode as the fallback

When sorting is not enough, Bulk API 1.0 offers a serial concurrency mode: batches are processed one at a time, no parallelism, no contention. Data Loader exposes it as a checkbox next to the Bulk API setting. Bulk API 2.0 does not have an equivalent, which is one of the few remaining reasons to use version 1.

The cost is the speed you went to Bulk for. A load that took twenty minutes in parallel can take hours in serial. Use it when the failure rate makes retries pointless, and use it on the specific object that is failing rather than the whole migration.

Measuring whether it helped

Do not assume. Three numbers, before and after:

  1. The count of lock errors. Pull the failed-records file and count rows where the message contains UNABLE_TO_LOCK_ROW. This is the number you are trying to move, and it should go to zero or near it.
  2. Total elapsed time, from job submission to the job reaching a completed state. Not the upload time. Poll the job and record the timestamps.
  3. Records per minute, which is the two divided, and the only one that tells you whether serial mode was worth it.

Run the same file twice, once sorted and once not, into a sandbox with production-shaped data. That is the only comparison that means anything, because the collision rate depends on how your records are distributed across parents and no general rule predicts it.

A word on retries: lock errors are transient, so resubmitting the failure file usually succeeds. That is a patch. If the retry file is thirty per cent of the load, you are running the load twice and calling it one load, and the time you think you saved is not real.

Frequently asked

Which column do I sort by if there are two parents? The one causing the locks, which is usually the master-detail parent or the one with roll-ups. If both contend, sort by the primary and accept that the secondary will still collide occasionally.

Does sorting help the REST path? Only if you are running requests concurrently. A single-threaded client never contends with itself, though it can still contend with other users.

Does this apply to updates as well as inserts? Yes, and to deletes. Any operation on a child that touches the parent takes the lock.