6

Previously, I would have assumed definitely not, but we recently ran into an issue where we have a large transaction (via VF page) which inserts a ton of rows under a parent object. Then it calls Database.execute() on a batch job, passing a single ID parameter to the job (the ID of the parent object, which has been inserted but not yet committed).

We have recently received a couple "List has no rows for assignment to SObject" exceptions from the batch job's start() method, which does a VERY simple query on the parent object. It's a real head scratcher. We thought possibly sharing rules could be involved, but it really doesn't appear to be the case.

Does anybody unequivocally know if it's possible for a Database.executeBatch() call to cause the batch to be executed prior to the entire calling transaction being committed? It seems the most plausible explanation at this point, but would be pretty surprising to me if it were true.

EDIT: just a little more data, when the query gets the "List has no rows for assignment to SObject" exception from the batch job, I can immediately run the same query manually and get results. This symptom also happens quite intermittently, less than 10% of the time. It only started occurring in the past two weeks. So far, we've only been able to replicate it on the EMEA instance.

3

Technically, a batch can't execute before the parent transaction commits, because before the final commit, the transaction could be rolled back, which means the batch must not execute until it is guaranteed that it will execute.

However, there are multiple processes involved, and in theory, if there are two separate systems, such an event could occur, assuming that batch processes data and normal salesforce data is stored in two separate systems with an IPC (InterProcess Communication) connection.

For this to be possible, I'd have to make a few assumptions. Standard disclaimer, I have not worked at salesforce.com as a developer, I'm just an exceptional programmer in general.

Assuming we have two systems, and each system maintains a separate database, and the commit of a transaction is dependent upon committing a batch for execution (say, to avoid promising a batch execute and then it disappears entirely), the clean up code might look something like this:

try {
    AsyncSystemBinding.queueBatches(transactionBatches);
    OracleBinding.commit(thisTransaction);
    OracleBinding.releaseLocks();
catch(Exception e) {
    AsyncSystemBinding.cancelBatches(transactionBatches);
    OracleBinding.rollback();
}

Yes, this is only theoretical code, but bear with me. So, in this case, we queue some batches, then commit. If we say that AsyncSystemBinding waits until the confirmation that the batches were successfully queued for execution, then it performs the final commit, this would cause a potential race condition. The final commit from Oracle may take just a few milliseconds, but during that time, the record wouldn't yet be visible to the batch.

My suggestion would be to try using a record lock on the query in the start method. This will force the system to wait for the transaction lock finishes, and should resolve any problems. Mind you, this is only theorycraft, but I'd be interesting in seeing the results, if you get back to this answer.

Try this:

Parent__c record = [SELECT Id FROM Parent__c WHERE Id = :parentId FOR UPDATE];

What?!

Okay, so I wrote a small class:

public class bax implements database.batchable<sobject> {
    public database.querylocator start(database.batchablecontext bc) {
        account a = [select id from account limit 1 for update];
        return database.getquerylocator([select id from contact where accountid = :a.id]);
    }

    public void execute(database.batchablecontext bc, sobject[] scope) {

    }

    public void finish(database.batchablecontext bc) {

    }
}

This works just fine, but when I called it from execute anonymous, I got bizarre results:

database.executebatch(new bax());

Here's the log from the batch apex:

22:10:47:030 EXECUTION_STARTED 

Here's the log from execute anonymous:

22:10:47:154 EXECUTION_STARTED 
22:10:47:158 SYSTEM_METHOD_ENTRY [1]|Database.executeBatch(APEX_OBJECT)

Note that the Batch Apex appears to execute 128 milliseconds before the function was called (124 milliseconds before the execute anonymous even started). I think there may be some clock skew between the two systems, and it would appear to support my idea that two systems could have a race condition, since clearly my Batch Apex Code can't execute backwards in time (or can it?).

I'm going to leave this as a community wiki, since I think it'd be valuable to gain insight from whomever else might experiment with what I've provided.

  • Thanks a lot, this seems to support my theory! I guess the nail in the coffin would be a modified test case like you posted, but where the execute anon process inserts the record that it is queried from the batch job. I'll try that when I get to fixing this issue and post my results. – jkraybill Aug 22 '14 at 4:51
  • Since the batch is executing in another separate context I think that at that very moment the batch has access to a snapshot of the database before you've inserted the records because they haven't been committed (they're only "committed" for that very same context in your original transaction). – Boris Bachovski Aug 22 '14 at 5:36
1

I think as soon as you call Database.executeBatch() it creates another completely separate transaction independent of your current apex transaction, which makes me believe that there might be a race condition going on here between the query in your batch and your commit in your class.

I'm not an expert in this and can't tell what is going on under the hood, but you might want to consider instead of calling executeBatch, rather schedule the batch in the next minute. That way it's guaranteed that database will commit before your batch runs.

I'd like to hear more from someone with more expertise and knowledge behind the scenes.

  • I'm going to write a proper answer later, but you have to remember that batches can't execute until the process that called it has committed fully-- any time a transaction fails, the entire effect of the transaction rolls back. No future calls, no scheduled jobs, no batches, no emails. – sfdcfox Aug 22 '14 at 1:55
  • Looking forward to find out what's the reason for this and how it's possible – Boris Bachovski Aug 22 '14 at 2:50
  • Yeah I am extremely puzzled. My understanding was definitely in line with sfdcfox's but the symptom I'm seeing is very odd. It also occurs quite intermittently, which seems to rule out security or a bad query. It seems to happen more often when the source transaction is large. I can't think of a plausible explanation other than a race condition, but that seems pretty unlikely as well! – jkraybill Aug 22 '14 at 4:03
  • @Bachovski - your suggestion is exactly what we are going to do in the absence of any other theories, thanks. Will update with results. – jkraybill Aug 22 '14 at 4:06
  • Try a record lock. Also, see my answer. – sfdcfox Aug 22 '14 at 4:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.