SOQL Record Locking : For Update
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 5 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.
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 ?
This is how I understand it. When you query a record with FOR UPDATE
, it will lock the record for that transaction. Anyone can get access to it through a standard SOQL query (aka not attempting to lock it) still, but if they attempt to access it with FOR UPDATE
as well, the system will return the QueryException
blocking both transactions from locking the record. The delay will occur on DML.
So, in the scenario where Transaction 1 locks a record with FOR UPDATE
(the record is locked), Transaction 2 queries and gets the record without FOR UPDATE
(the record is not locked), when DML is attempted on that record in Transaction 2, it will give a brief delay to wait for the lock from Transaction 1 to release (if it hasn't already). If it does not release, Transaction 2 will return the UNABLE_TO_LOCK_ROW error.
Q2 : In the above case will the second thread get access to the record ? or the query will not return any record ?
Once a record is queried, it is locked for the remainder of the transaction. Even if you change the values that caused the record to return, that record will still be locked. In the above scenario, Transcation 2 won't be able to access the record until Transaction 1 is completely finished running (assuming both were attempting to query with FOR UPDATE
).
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 ?
According to the docs, the lock gets released when the transaction completes.