5

I'm attempting a mass update on Contacts via Execute Anonymous. My code gives me the following error:

System.LimitException: Apex CPU Time Limit Exceeded

Here's my code:

List <Contact> contactList = [select Id, Mass_Update_Checkbox__c from Contact Limit 1000]; 
for (Contact contact : contactList) {
    contact.Mass_Update_Checkbox__c = TRUE; 
} 
update contactList;
1
7

I actually have a Swiss Army Knife Batch Processing class that I use for occasions like this. I use it in orgs where there's a lot of "traffic" going on, and I need to quickly update a large number of records, but I don't want to query the data, update spreadsheets, and put the data back. This code allows me to do this:

Database.executeBatch(
  new ExecAnonBatch(
    Generators.fromQuery('select id from Contact'),
    Executors.updateField(Contact.Mass_Update_Checkbox__c, true)));

Basically, anyone with Author Apex code can perform a mass update on all records in the system with a simple script. Note that I called it a Swiss Army Knife; the complete class also includes creating records, deleting records, undeleting records, sending emails, and a few other tricks I like to have available on hand. This is part of what will be available in my open-source library when it's ready.

Here's the relevant portions (n.b. I'm typing this from scratch, there may be a few typos):

public class ExecAnonBatch implements Database.Batchable<Object> {
  public interface generator {
    Object generate();
  }
  public interface executor {
    void execute(Object[] values);
  }
  generator gen;
  executor exec;

  public ExecAnonBatch(generator theGenerator, executor theExecutor) {
    gen = theGenerator;
    exec = theExecutor;
  }
  public Iterable<Object> start(Database.BatchableContext context) {
    return (Iterable<Object>) gen.generate();
  }
  public void execute(Database.BatchableContext context, Object[] scope) {
    exec.execute(scope);
  }
  public void finish(Database.BatchableContext context) {
  }
}

public class Generators {
  class QueryGenerator implements ExecAnonBatch.generator {
    String queryString;
    QueryGenerator(String query) {
      queryString = query;
    }
    public Object generate() {
      return Database.getQueryLocator(queryString);
    }
  }
  public static ExecAnonBatch.generator fromQuery(String query) {
    return new QueryGenerator(query);
  }
}

public class Executors {
  class FieldUpdate implements ExecAnonBatch.executor {
    SObjectField field;
    Object value;
    FieldUpdate(SObjectField updateField, Object fieldValue) {
      field = updateField;
      value = fieldValue;
    }
    public void execute(Object[] scope) {
      SObject[] records = (SObject[])scope;
      for(SObject record: records) {
        record.put(field, value);
      }
      Database.update(records, false);
    }
  }
  public static ExecAnonBatch.executor updateField(SObjectField field, Object value) {
    return new FieldUpdate(field, value);
  }
}

Now with unit tests, too:

@isTest class ExecAnonBatchTest {
  @isTest static void test() {
    Test.startTest();
    Database.executeBatch(
      new ExecAnonBatch(
        Generators.fromQuery('select id from user limit 1'),
        Executors.updateField(User.LastName, 'Doe')));
    Test.stopTest();
    System.assertEquals('Doe', [SELECT LastName FROM User ORDER BY LastModifiedDate DESC LIMIT 1].LastName);
  }
}

Install this package: Production/Developer/Trial | Sandbox

11
  • That solution seems like overkill for a generic dev/admin that may already be out of their depth. Though I sure could have used it last week for a similar situation! Impersonation for bulk updates is nice. – Adrian Larson Jan 10 '17 at 20:38
  • 1
    @BrooksBruce It requires a little bit of initial setup, so I created a package for you. Click on it, install it, and then you can use the exec anon as described in the answer. A few minor fixes were made, but I'll include them in a forthcoming edit. – sfdcfox Jan 10 '17 at 21:03
  • 1
    Once again I'm blown away by the sheer elegance of the design here. In defense of Data Loader is if you have errors (perhaps from unexpected validation rules interfering with your mass update) you have a convenient retry mechanism by using the errorXXXXX.csv file. – cropredy Jan 10 '17 at 23:00
  • 1
    @cropredy Yeah, this the most basic framework. A more robust version might collect the errors or something and report them somehow. I usually use this script when I know it won't fail, or I at least pull up a report afterwards to verify the results. – sfdcfox Jan 10 '17 at 23:08
  • 1
    @NathanaelSchmolze Right, whatever is serialized at the moment of Database.executeBatch will remain so for the lifetime of the batch, even without Database.Stateful. My new version actually includes stateful storage as an API, too, but again, this answer was just meant to be a sample. – sfdcfox Apr 22 at 1:39
5

The reason why you get this error is you have too much logic in your Contact triggers to allow the operation to succeed. The first thing you need to understand is that standard trigger batching process your records in chunks of 200. You have 10 seconds to complete the transaction, so that gives you 10ms per record. If your trigger logic consumes 20ms per record, you need to change your batch size to 100. If it takes 2500ms per record, you're down to four records per batch.

In Execute Anonymous, you don't have granular control over the batch size. You should instead use Data Loader to perform this functionality. From there, you can just lower the batch size if you run into this error. If performance is really bad, you can take the batch size all the way down to 1, though the default is 200. Just export this query:

SELECT Id FROM Contact WHERE Mass_Update_Checkbox__c = false

Make sure you modify the resulting csv file to add a column for Mass_Update_Checkbox__c and add a value of true for each row.

3
  • Thanks, Adrian. Any particular reason why this won't work via Execute Anonymous? – Brooks Bruce Jan 10 '17 at 19:44
  • 1
    @BrooksBruce Added a paragraph to explain why. Your trigger logic is too heavy to allow operations in the standard trigger batching size. – Adrian Larson Jan 10 '17 at 19:48
  • 4
    not to mention that w/ execute anonymous, if trigger recursion via static variables isn't implemented properly, one can find that rows 201+ are never processed by the trigger – cropredy Jan 10 '17 at 23:01

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.