All Salesforce Developers – especially the ones working on enterprise-level projects – should have an in-depth understanding of how Salesforce works and behaves. When deciding on key solutions for projects with high traffic volume, it is crucial for Salesforce Developers to be mindful of Salesforce limitations, execution context, order of execution, and other relevant factors.
One of the most crucial parts is record locking. In this article, I will go over how record locking works.
Default Record Locking in Salesforce
As in other relational databases, Salesforce also has a record-locking mechanism. Record lock is used to maintain data integrity, so when DML operation is performed on certain records, they are locked and other processes can do DML on the same records only after the previous execution context is ended.
For example, if process A is updating record C, process B cannot perform a DML operation on the same record until process A’s execution context is completed.
When B attempts to do DML on the record, it will wait a maximum of 10 seconds for A to be completed. If it takes longer, then B fails to obtain exclusive access to the record and a similar DmlException will occur: UNABLE_TO_LOCK_ROW, unable to obtain exclusive access to this record or 1 records: <record Id goes here>.
When the record is locked, due to the current ongoing execution context, the other execution context will be able to read the record, but it will be the version of the record that is committed to the database.
The execution context that locked the record is still in progress. This means that a new version of the record is saved to the database but not committed yet. The other execution context will read the “old version” which is in the database currently.
However, the execution context that locks the record can read the saved version and can also do DML operations without the need to re-lock the record, as the record is already locked for the execution context.
The record remains locked from the initiation of the DML operation until the execution context is completed.
When two simultaneous transactions attempt to perform DML on the same record, the second transaction will briefly wait for the first to complete. If the second transaction can then obtain exclusive access to the record, its DML operation will also succeed.
The second transaction will not technically override the first transaction, as the first transaction has already been committed to the database.
If the second transaction is initiated from the UI, Salesforce will prompt the user to choose between keeping the changes from the first transaction or saving the new changes from the second transaction.

However, there might be an issue with default record locking and you might need something else (which I will discuss in the next section of this article).
What Is FOR UPDATE?
Let’s reflect on two important points that we discussed earlier in the article:
- The record is locked between points of DML initiated and the execution context completed.
- Execution context is able to read the record that is locked by another execution context. However, it reads the version that is already committed in the database and not the version that another execution context is currently creating.
These both might cause you some issues – here’s why.
Let’s assume an Opportunity record has an Amount value of 100. User A updates the Amount to 150 via the UI. At the same time, User B clicks a button that triggers a process to increment the Amount by 30. User B’s process queries the committed Amount value, which is still 100 because User A’s update hasn’t been committed yet.
B is incrementing by 30 which will be 100 + 30 = 130. Since B initiates a DML operation while A is still in progress, Salesforce waits for A to complete. When A is finished, the Amount becomes 150. So B can save and commit a new value to the database, which is 130. But the result must have been 180.
In order to avoid such scenarios, the execution context must be able to obtain exclusive access to the record – in other words, lock the record while reading it.
So the record should not be locked between points of DML initiated and the execution context completed but instead between points of querying and the execution context completed.
So we want to wait for other execution contexts to be completed and subsequently query the record, which will guarantee that we will read the fresh version of the data.
In order to achieve that behavior, we have to add FOR UPDATE in the queries: [SELECT Id, Amount FROM Opportunity WHERE Id=:recordId FOR UPDATE].
The query with FOR UPDATE
will try to obtain exclusive access to the record for 10 seconds maximum.
If in that period of time the record is still locked by another transaction, then you will receive the following QueryException: “Record Currently Unavailable: The record you are attempting to edit, or one of its related records, is currently being modified by another user. Please try again.“
Which Records Does Salesforce Lock?
When DML operations are performed, the record being operated on is locked. Additionally, any required or “Don’t allow deletion of the lookup record that’s part of a lookup relationship” parent (master) lookups are also locked. This also applies to any parent record that is part of a standard relationship that behaves like a master-detail relationship (e.g., the standard Account and Contact relationship).
So when you do DML on a Contact object, the parent Account is also locked, meaning that other execution contexts will need to wait to obtain exclusive access in order to update Account or Contact as both will be locked.
Additionally, updating a child record also locks the parent record, which means that the child’s sibling records are also technically locked. This is because any attempt to update a sibling record will also require locking the parent record. Therefore, the update to the sibling record must wait for the completion of any other execution context that has already locked the same parent record.
Considerations While Deploying Data with ETL Tool
If we deploy data with ETL tool using Bulk API, we should avoid parallel processing. This is because records will be split into 200 records size batches, and records from different chunks will potentially attempt to lock the same parent record.
Serial processing should be preferred and data should be sorted by the parent before deploying.
If we have a junction object, we should sort it by one of the parents which we think has more chance to produce an error depending on what logic we have in the codebase.
Summary
Large organizations with concurrent transactions that perform DML on the same record may encounter concurrency issues if the initial transaction takes too long, causing others to wait for exclusive access. To prevent these concurrency issues, it is important to understand how they occur and take preventative measures:
- Follow best practices and keep the execution contexts short by writing efficient code.
- Try to put the DML operations at the end of the transactions, this will shorten the amount of the time of locking the records.
- Use
FOR UPDATE
wisely, it is not a good practice to use it everywhere, as it locks the record from the query until the execution context. Use it when it is crucial to read the most fresh data, for example when you are dealing with money-related stuff. - Avoid relating more than 10,000 child records to the Parent record to avoid Lookup Data skew, as there is a higher chance to encounter a concurrency issue.
Let us know your thoughts in the comments below!