[SalesForce] SOQL Record Locking : For Update

Well I was trying to Implement Record Locking apex but Salesforce Document doesn't seem to help much. Even tried searching stackexchange there was a old question but the answer form StackExchange and details from document seems to be conflicting.

According to Salesforce Doc whenever a second transaction tries to lock a record that is already locked it will throw an "QueryException" whereas from the Stackexchange it seems like the second thread will actually wait.

Q1: I am bit confused about what will happen ? Will it fail(throw exception) or will it wait for 4-5 seconds and then if it doesn't get
access it will throw an exception ?

In production we are facing a issue where second thread seems to get access to the record even though we have put a lock, second transaction seems to get access, may be after sometime but doesn't seems to throw any exception immediately. For example we have a method

public static void updateMyAccount(){
Account acc = [SELECT Id FROM Account WHERE isValid = TRUE AND Id='SOME ID' FOR UPDATE];
//some time consuming process...callouts etc here
 acc.isValid__c = false;
 update acc;

}

Scenario:(assuming the system waits)

  • Lets assume Thread 1 calls the above method and acquires a lock on the RECORD.
  • Now while Transaction 1 is processing the record another Thread Transaction 2 tries to access the record and waits for the lock to release
  • While the transaction 2 is waiting thread one changes "isValid__c" to false and the record no more matches the query condition.

Q2 : In the above case will the second thread get access to the record ? or the query will not return any record ?

Q3 : When does the lock gets released ? the Salesforce doc doesn't seems to provide much insight into it. does the lock get
released after a DML ? or it waits for full transaction to complete ?

Update 1: Actual Production Scenario
In production leads are generally created using web to lead form. We have a page with a button that assign records by pulling records from a queue and changes owner to the current user.

The query looks like

    Lead l = Database.query('SELECT Id,Name ,CreatedDate FROM Leads__c WHERE  Lead_Status__c =\'Open\' AND Owner.Name = 'My Queue' LIMIT 1 FOR UPDATE');
//some calculation 
l.OwnerId = UserInfo.getUserId();

My assumption was when the second user will try to access the same record(by using the same query) it will throw an error. But some how records are getting updated. There are some suspicious records in production where if I check the history

  • User 1 Changed Owner from My queue to User 1 [Same TimeStamp]
  • User 2 Changed Owner from User 1 to User 2 [Same TimeStamp]

The above observation makes me suspicious that the code actually waits. But again the docs 🙁 . What makes it really hard is this situation is really hard to replicate and almost impossible.

Best Answer

Q1: I am bit confused about what will happen ? Will it fail(throw exception) or will it wait for 4-5 seconds and then if it doesn't get access it will throw an exception?

You will get somewhere around ten seconds before it will exclusively fail. The user will be advised to try their save again. The status code will be "UNABLE_TO_LOCK_ROW".

Q2 : In the above case will the second thread get access to the record ? or the query will not return any record ?

Either the prior transaction has already completed, so it would not appear in the results, or you'll get an error because the row can't be locked. There's never a scenario where it would be returned with non-matching criteria.

Q3 : When does the lock gets released ? the Salesforce doc doesn't seems to provide much insight into it. does the lock get released after a DML ? or it waits for full transaction to complete ?

The documentation clearly states:

While the records are locked by a client, the locking client can modify their field values in the database in the same transaction. Other clients have to wait until the transaction completes and the records are no longer locked before being able to update the same records. Other clients can still query the same records while they’re locked.

(Emphasis mine)

Clarification The lock isn't released until sometime after the final commit occurs, usually within a few milliseconds, although post-transaction logic can re-acquire the lock immediately, such as future methods.


So, I decided to test this with some code to get a more definitive answer.

First, I set code that ran around (but less than) 10 seconds, like this:

Controller

public with sharing class rowlock {
    Id accountId = [SELECT Id, name FROM Account where name = 'test' LIMIT 1].Id;

    public void updateAccount() {
        Account[] a = [SELECT Id FROM Account WHERE Id = :accountId and name = 'test' FOR UPDATE];
        for(integer i = 0; i < 3000000; i++) {
            // Do nothing, haha!
        }
        if(!a.isempty()) {
            a[0].name = 'test2';
            update a[0];
        }
        system.debug(logginglevel.error, a.isempty());
    }
}

Page

<apex:page controller="rowlock">
<apex:form id="form">
<script>
addEventListener("load", function() {
    updateAccount();
    updateAccount();
}, true);
</script>
    <apex:actionFunction name="updateAccount" action="{!updateAccount}" reRender="form"/>
    </apex:form>
</apex:page>

During this trial run, the page bombed out on the second transaction with "Record Currently Unavailable..."

Changing 3,000,000 to 100,000, both transactions passed. However, the first transaction (the one with the first lock) returned 1 row, while the second transaction returned 0 rows, thus proving that the lock actually resulted in the query results being modified to exclude the locked record since it no longer met the criteria.


Edit: Fixed the status code to current terminology, cleaned up the multiple strke-throughs, which were distracting.

Related Topic