Salesforce’s multitenant architecture ensures that resources are shared fairly among tenants. To maintain performance and stability, Salesforce enforces governor limits—thresholds that Apex code must stay within during execution.
This blog post demystifies Apex transactions, explains critical governor limits, and provides actionable strategies to ensure your automation scales without breaking.
An Apex transaction is a set of operations that execute as a single unit. If one operation fails, the entire transaction is rolled back.
Each transaction is bounded by its governor limits.
Salesforce’s multitenancy model means thousands of orgs run on the same infrastructure. Governor limits:
Limit | Value (Synchronous) |
---|---|
Total SOQL queries | 100 |
Total DML statements | 150 |
CPU time | 10,000 ms |
Heap size | 6 MB |
Callouts | 100 |
Records retrieved by SOQL | 50,000 |
Future method calls | 50 |
Queueable jobs added | 50 |
Note: Asynchronous limits differ and are generally more lenient.
Use the Limits
class to monitor resource usage in real-time:
System.debug('SOQL queries used: ' + Limits.getQueries());System.debug('SOQL queries limit: ' + Limits.getLimitQueries());
You can also use this dynamically in logic to prevent limit overrun.
Never assume one record per transaction. Use Trigger.new
as a collection.
Bad:
for (Account acc : Trigger.new) {insert new Contact(LastName = 'Test', AccountId = acc.Id);}
Good:
List<Contact> contacts = new List<Contact>();for (Account acc : Trigger.new) {contacts.add(new Contact(LastName = 'Test', AccountId = acc.Id));}insert contacts;
Querying or performing DML inside loops is a surefire way to hit limits.
Aggregate data using maps and sets to optimize lookups and avoid duplicate processing.
When your code needs to do more than what synchronous limits allow, go asynchronous.
Each has its own use case and limit thresholds.
public class MyQueueableJob implements Queueable {public void execute(QueueableContext context) {// logic here}}System.enqueueJob(new MyQueueableJob());
Use Queueables for chaining jobs and handling larger data volumes.
A single DML action (e.g., mass update of records) can fire multiple triggers, but they execute as one transaction.
This means:
Always test bulk scenarios to ensure compliance under realistic data volumes.
Use try/catch blocks to gracefully handle exceptions and prevent full rollbacks when possible.
try {insert someRecords;} catch (DmlException ex) {// Log error, send notification}
Offload operations to asynchronous subscribers via Platform Events.
If working with large data volumes, process them in smaller chunks to stay under limits.
In Queueable Apex, use System.finalizer
to run post-processing logic regardless of success/failure.
Suppose you’re reassigning 10,000 leads to a new owner.
Bad Approach:
for (Lead l : allLeads) {l.OwnerId = newOwnerId;update l;}
Good Approach:
Understanding Apex transactions and governor limits is fundamental for writing efficient, scalable, and reliable Salesforce code. Whether you’re optimizing triggers, classes, or integrations, respect for limits ensures your org stays stable and your automation performs flawlessly.
Need help optimizing a transaction-heavy process? Drop your scenario in the comments and let’s troubleshoot together!
Quick Links
Legal Stuff