No. FOR UPDATE is not supported in Database.QueryLocator queries. Try it and Salesforce throws:
“FOR UPDATE clause cannot be used here.”
The reason is structural: FOR UPDATE locks records for the duration of the current transaction. Batch Apex runs each chunk in its own transaction. The lock from start couldn’t realistically span dozens or thousands of separate chunk transactions — so the platform forbids it altogether.
What you tried
public Database.QueryLocator start(Database.BatchableContext ctx) {
// Compile error: FOR UPDATE not allowed here
return Database.getQueryLocator(
'SELECT Id, Status FROM Case WHERE Status = \'New\' FOR UPDATE'
);
}
How locking actually works in batch
Each execute invocation starts a fresh transaction. Inside that transaction, you can FOR UPDATE if you want:
public void execute(Database.BatchableContext ctx, List<Case> scope) {
Set<Id> ids = new Map<Id, Case>(scope).keySet();
// Lock the chunk's records for the duration of *this* transaction
List<Case> locked = [SELECT Id, Status FROM Case WHERE Id IN :ids FOR UPDATE];
for (Case c : locked) c.Status = 'Auto-Closed';
update locked;
}
The lock is held only during the chunk’s transaction. When execute returns, the lock releases. The next chunk acquires its own lock for its own records.
Why per-chunk locking is usually fine
The whole point of FOR UPDATE is to prevent another transaction from modifying a record while you’re working on it. In batch, the records in each chunk are different — so you only need to lock the current chunk’s records, not the entire dataset.
If a user happens to edit a record while its chunk is processing, the lock prevents it. If a user edits a record from chunk 5 while chunk 3 is running, that’s irrelevant — chunk 5 hasn’t started yet.
When you genuinely need cross-chunk locking
If you really do need to lock the entire dataset (rare), batch isn’t the right tool. Options:
| Goal | Tool |
|---|---|
| Lock & update millions of records | Don’t. Re-think the design. |
| Lock & update a few hundred records | Synchronous code in a single transaction with FOR UPDATE |
| Process records carefully with optimistic concurrency | Add a Version__c field, check-and-set in DML |
| Prevent edits during a long job | Use record-level flag (e.g., Processing__c = true) + validation rule |
Common interview follow-ups
- Does this restriction also apply to Queueable? — Queueable can use
FOR UPDATEinsideexecute(single transaction). It’s fine because Queueable runs one transaction per job — not one per chunk. - Can
IterablestartuseFOR UPDATE? — No. The same restriction applies to thestartquery regardless of return type. - What about
SELECT ... WITH SECURITY_ENFORCED? — That’s allowed instart. OnlyFOR UPDATEis blocked.
Verified against: SOQL and SOSL Reference — FOR UPDATE. Last reviewed 2026-05-17.