Given a scenario, identify the implications of governor limits on Apex transactions.

After studying this topic, you should be able to:

  • Determine what governor limits and Apex transactions are
  • Describe the implications of governor limits on Apex transactions

Table of Contents

  • Governor Limits
  • Best Practices & Considerations
  • Scenarios and Solutions

Introduction

  • Salesforce enforces governor limits to ensure resources are shared in the multi-tenant environment - these limits impact developers' coding practices:
    • DML Statements: DML statements in an Apex transaction are rolled back if a governor limit is exceeded
    • Query Inside Loop: A SOQL query inside a for loop can result in exceeding the governor limit
    • Batch Apex: Batch Apex processes smaller batches of records to avoid exceeding governor limits
    • Count() Function: Using the count() function in a SOQL query counts as one query row towards the limit
    • Limits Methods: limits methods allow returning the specific limit for a particular governor

Governor Limits

  • Governor Limits ensure that one customer does not monopolize shared resources in a multi-tenant environment
    • Example Limits: CPU time, memory used, how long a query can run, how many records are returned from a query
    • Performance: Limits are monitored per transaction and per customer over a defined time period to ensure that performance in one org is not impacted bby another
  • Transactions are also know as execution contexts
    • Transactions are defined as a set of operations that is executed and evaluated as a single unit
      • Each event that occurs in a single transaction is bound by their associated governor limits
    • Can be initiated from various sources in the Salesforce platform:
      • Apex triggers, class methods, anonymous code, web service, Visualforce page, custom Lightning component, process, Flow, etc

  • Exceeding Governor Limits resuls in:
    • Terminated: current transaction is immediately terminated and unrecoverable
    • Limit Excelption: the System.LimitException is thrown - this exception cannot be handled
    • Roll Back: entire transaction is rolled back and no data is committed to the database

  • Limit on SOQL Queries: 100 queries per synchronous transaction, 200 queries per asynchronous transaction
    • 101st query in a synchronous transaction causes the limit exception to be thrown
// The following throws a System.LimitException
List<Account> result;
for(Integer x=0; x< 101; x++>) {
    try{
        result = [SELECT Name FROM Account LIMIT 10];
        System.debug('Queries: ' + Limits.getQueries());
        System.debug('Query rows: ' + Limits.getQueryRows());
    }catch(Exception ex){
        System.debug('Caught Exception');
        System.debug(ex);
    }
}

  • Limit on DML Statements: 150 DML statements of ANY kind per transaction (insert, update, upsert, delete, undelete, merge, emptyRecycleBin)
// The following throws a System.LimitException
for (Integer x=0; x<200; x++>) {
    Account newAccount = new Account(Name='MyAccount-' + x);
    try {
        insert newAccount;
        System.debug(Limits.getDMLStatements());
    } catch (Exception ex) {
        System.debug('Caught Exception');
        System.debug(ex);
    }
}
insert new Account(Name='MyAccount-last');

  • Limit on Total Stack Depth: limit enforced on the total stack depth in a transaction that recursively fires triggers
    • Ex: trigger invokes another trigger that then invokes the trigger that invoked it, creating a recursive loop
    • Max depth of 16 for recursive Apex triggers
trigger AccountTrigger on Account (after update) {
    for (Account a : Trigger.new) {
        List<Contact contacts> = [SELECT Id, Description 
                                    FROM Contact 
                                   WHERE AccountId = :a.Id];
        for (Contact c : contacts) {
            c.Description = Test;
            update c;
        }
    }
}

  • Limit on Total Heap Size: heap size is the amount of memory allocated to aan object during a transaction
    • Synchronous heap size limit: 6 MB
    • Asynchronous heap size limit: 12 MB
    • Ex: a list collection is used for storing the results of a SOQL query - memory allocated for the variable grows exponentially as more and more records are added to the collection.

Best Practices & Considerations

  • Limits Apex Class: used to retrieve information for resources that have associated governor limits. Most common methods of the class:
    • getQueries() / getLimitQueries(): number of SOQL Queries issued / allowed
    • getQueryRows() / getLimitQueryRows(): number of records returned / allowed by SOQL queries
    • getDMLStatements() / getLimitDMLStatements(): number of DML statements issued / allowed, such as insert, update, etc
    • getDMLRows() / getLimitDMLRows(): number of records processed / alowed by any DML statement
    • getHeapSize() / getLimitHeapSize(): approximate memory used / allowed for the heap

  • Best practices & considerations:
    1. Database Rollback: DML operations will be rolled back when limits area exceeded
    2. Avoid SOQL inside loops
    3. Run within the Hea Size Limit: Use SOQL for-loops to process records in batches, especially when handling large data sets
      • Use transient variables for Visualforce pages
      • Remove items in a collection after use
      • Check the Limits.getHeapSize() and Limits.getLimitHeapSize() methods
    4. Count Function: SOQL queries that use the COUNT() or COUNT(fieldname) function to return an integer only counts as one query row toward the governor limit
    5. SOQL in Apex Trigger: ex: SOQL inside a trigger.new loop may exceed the max SOQL queries allowed
    6. Large number of records: use Batch Apex to break the record set down to smaller batches so governor limits are not reached
    7. DML Rows and Limits: use Limits.getDMLRows() aand Limits.getDMLRows()
// Use Limits methods as shown below to determine whether code should
// continue or abort an operation
System.debug('SOQL queries performed: ' + Limits.getQueries() + 
             ' of ' + Limits.getLimitQueries());
System.debug('Number of records queried: ' + Limits.getQueryRows() + 
             ' of ' + Limits.getLimitQueryRows());
System.debug('DML statements issued: ' +  Limits.getDmlStatements() + 
             ' of ' + Limits.getLimitDmlStatements());

if (Limits.getQueries() >= Limits.getLimitQueries()) {
    System.debug('Performing another SOQL query will exceed the governor limits.');
} else {
    List<Opportunity> records = [SELECT Id, Name, CloseDate, StageName 
                                   FROM Opportunity 
                                  WHERE AccountId IN :accountIds];
    
    if ((records.size() + Limits.getDMLRows()) > Limits.getLimitDMLRows()) {
        System.debug('Updating another record will exceed the governor limits.');
    } elseif (Limits.getDmlStatements() >= Limits.getLimitDmlStatements()) {
        System.debug('Performing another DML statement will exceed the governor limits.');
    } else {
        System.debug('You may proceed updating the records...');
    }
}
  • Example below shows how a SOQL for-loop and a list for-loop can be used to avoid hitting governor limits
    • List iteration helps prevent exceeding the heap size limit when handling large data volumes
    • Note version 3, below, will fail if there are more than 10,000 records - if so, use Limit methods to prevent failures
// In this example, there are 10,000 Lead records to process. 
// Version 3 represents the recommended solution.

// Version 1: DML statements limit of 150 is easily exceeded
for (Lead ulead : [SELECT Id FROM Lead]) {
    // modify lead...
    update ulead;
}

// Version 2: DML rows limit of 10,000 is exceeded (or synchronous 
// heap size limit of 6MB as list grows and other fields were included)
List<Lead> uleads = new List<Lead>();
for (Lead l : [SELECT Id FROM Lead]) {
    // modify lead...
    uleads.add(l);
}
update uleads;

// Version 3: Total DML statements issued is 50 only since a SOQL 
// for-loop automatically processes records in batches of 200
for (List<Lead> leads : [SELECT Id FROM Lead]) {
    List<Lead> uleads = new List<Lead>();
    for (Lead l : leads) {
        // modify lead...
        uleads.add(l);
    }
    update uleads;
}

Scenarios and Solutions

Reference FoF Slides