I think you're accidentally conflating the two approaches we can take when it comes to SOQL for loops.
- Have the loop variable be a single SObject instance (e.g.
for(Contact cont :[query here])
)
- Have the loop variable be a
List<SObject>
(e.g. for(List<Contact> contactGroup :[query here])
)
The single object loop variable version is generally what you'll see.
I think the list loop variable approach is mainly a holdover from years ago when we had a "script statement" governor limit instead of the CPU governor limit (but it may still be useful in some situations).
In the first approach, for(Contact cont :[query here])
the loop variable cont
only holds a single SObject instance at a time (because that's as much as it can hold). The result of the query itself holds some number of records (up to 2000, I believe), which may also count against your heap space1. Once you iterate over the first batch of records, an internal call is made to queryMore()
to fetch the next set of records to keep feeding your loop.
In the second approach, for(List<Contact> contactGroup :[query here])
This seems to be the approach you're asking about.
The loop variable is a List, which can hold more than one SObject instance. The quote that you've pulled from documentation doesn't apply to this approach. Salesforce will fill contactGroup
with 200 records at a time instead of just a single record.
From there, you'd then either use a nested loop to operate on one at a time or pass to another method (which would likely contain a loop to iterate over them one at a time).
In both approaches
Having the query feed the loop directly will help reduce the heap space used because you only have a portion of the records returned by the query "in memory" at any given time.
Once a piece of data is no longer used, the heap space it consumes can be freed (which happens as part of the "garbage collection" that is occasionally run by Salesforce).
Of course, if you have a collection defined outside of your loop, and simply add entire records to it inside your loop, you've negated the heap space advantage that the SOQL for loop would have given you.
// If you do something like this, you negate the advantages of a SOQL for loop
List<Contact> contacts = new List<Contact>();
for(Contact cont :[query here]){
contacts.add(cont);
}
Also, keep in mind
While Salesforce only counts each record as 2kB towards your org's data storage limit, when you work with that data in Apex it can take up more (or less) heap space than that.
When working in Apex, every bit is counted.
List<Contact> list1 = new List<Contact>();
List<Contact> list2 = new List<Contact>();
Integer count = 2000;
Integer heapBefore1, heapBefore2, heapAfter1, heapAfter2;
heapBefore1 = Limits.getHeapSize();
for(Integer i = 0; i < count; i++){
list1.add(new Contact(LastName = 'Holmes'));
}
heapAfter1 = Limits.getHeapSize();
heapBefore2 = Limits.getHeapSize();
for(Integer i = 0; i < count; i++){
list1.add(new Contact(LastName = 'Watson', FirstName = 'John'));
}
heapAfter2 = Limits.getHeapSize();
system.debug('heap usage 1: ' + (heapAfter1 - heapBefore1)); // prints 52020
system.debug('heap usage 2: ' + (heapAfter2 - heapBefore2)); // prints 86016
1: I'm not entirely sure how to test this to determine if it's true or not. My first attempt at figuring that out appears to show that this may be true for the first 200 records or so, but then my heap space dropped greatly for the remaining 219 records I had. Perhaps this is Salesforce realizing I'm not doing anything with the data, and optimizing some things out.