System.LimitException: Too many query rows: 50001
What does this error mean?
Even in Batch Apex, each execute() method invocation has its own fresh governor limits — including the 50,000-row query limit. The batch framework paginating through 50 million records via Database.getQueryLocator() does not override the row limit for individual queries within each execute chunk. If your execute() method issues its own SOQL queries that return more than 50,000 rows combined, this error is thrown for that chunk.
Common Causes
1. Unbounded query inside execute() on a related large object
The batch processes Account records in chunks, but inside execute(), a query fetches all Contacts for every account in the chunk — potentially hundreds of contacts per account times 200 accounts equals tens of thousands of contact rows.
2. Accumulating rows across multiple queries in execute()
Multiple queries each returning thousands of rows — the 50,000 limit is a cumulative limit across all queries in one execute call, not per-query.
How to Fix It
Solution 1: Reduce batch chunk size
Lowering the batch size means fewer parent records per execute() call, which proportionally reduces the number of child records fetched per chunk.
// Reduce batch size to lower rows-per-execute
Database.executeBatch(new MyBatch(), 50); // was 200, now 50
// Even smaller for objects with many children per parent
Database.executeBatch(new MyBatch(), 10);
Solution 2: Scope the inner query to the batch scope
global void execute(Database.BatchableContext bc, List<Account> scope) {{
Set<Id> accountIds = new Map<Id, Account>(scope).keySet();
// ✅ Always scope inner queries to the current batch chunk
List<Contact> contacts = [
SELECT Id, Name, AccountId
FROM Contact
WHERE AccountId IN :accountIds
LIMIT 49000 // safety ceiling
];
// process contacts...
}}
Solution 3: Change the batch to process child records directly
Instead of batching parent records and querying children inside execute(), change the batch to use a getQueryLocator() that iterates directly over the child object — giving you full batch-level control over the volume.
Pro Tip: Inside a Batch Apex execute(), use Limits.getQueryRows() to monitor cumulative row consumption as you process. If you approach 40,000 rows, that's a signal to lower your batch size or restructure the execute logic.