Find and Fix Slow SOQL Queries in Salesforce Debug Logs
To find SOQL problems in a Salesforce debug log, look at the SOQL_EXECUTE_BEGIN/END event pairs: repeated identical queries mean a query in a loop (the cause of "Too many SOQL queries: 101"), and large row counts on SOQL_EXECUTE_END flag unselective or unbounded queries. Fix them by querying once outside the loop, filtering on indexed fields, and using maps for lookups.
Reading SOQL events in the log
With the Database log category at FINEST, every query produces a pair:
10:41:12.3 (98214532)|SOQL_EXECUTE_BEGIN|[27]|Aggregations:0|SELECT Id, StageName FROM Opportunity WHERE AccountId = :acctId
10:41:12.3 (99871220)|SOQL_EXECUTE_END|[27]|Rows:1
[27]— the Apex line that issued the query. Your jump-to-code pointer.Rows:1— rows returned. One row fetched 200 times is a loop query; 48,000 rows once is an unbounded query.- The elapsed-counter difference between BEGIN and END is the query's wall time.
The three SOQL patterns that hurt you
1. Queries in loops → "Too many SOQL queries: 101"
The signature in the log is the same query text at the same line number, dozens of times. The fix is always the same shape:
// Bad — one query per record (fires 200 times for a bulk update)
for (Opportunity opp : Trigger.new) {
Account a = [SELECT OwnerId FROM Account WHERE Id = :opp.AccountId];
}
// Good — one query, then map lookups
Set<Id> acctIds = new Set<Id>();
for (Opportunity opp : Trigger.new) { acctIds.add(opp.AccountId); }
Map<Id, Account> accts = new Map<Id, Account>(
[SELECT OwnerId FROM Account WHERE Id IN :acctIds]);
for (Opportunity opp : Trigger.new) {
Account a = accts.get(opp.AccountId);
}
If the repeated query isn't in your code, suspect trigger recursion or a managed package — the log's execution tree shows who issued it.
2. Non-selective queries
A query is selective when its filter can use an index. These defeat indexes:
- Filtering on non-indexed custom fields (standard indexes: Id, Name, OwnerId, CreatedDate, SystemModstamp, lookup/master-detail fields, External IDs and unique fields).
- Leading wildcards:
LIKE '%acme'. - Negative operators:
!=,NOT IN,EXCLUDES. - Comparisons to
nullon non-indexed fields.
On objects with over ~200,000 records, non-selective filters in trigger context can throw "Non-selective query" runtime errors. Fixes: filter on indexed fields, add a custom index via Salesforce Support, or use an External ID field. The Salesforce query optimizer docs cover selectivity thresholds in detail.
3. Unbounded row counts
A single query returning tens of thousands of rows eats the 50,000 query-row governor limit, bloats heap, and slows everything. Add filters, add LIMIT, or process with a SOQL for-loop / Batch Apex, which chunk results instead of materializing them all.
Aggregate view: the fast way
Scrolling a 40,000-line log to hand-count query repetitions is the archaeology ForceLens exists to end. Its SOQL tab aggregates every query in the transaction — count, total rows, originating code unit — so a query-in-a-loop shows up as "×200" on one row, and the unbounded query tops the rows column. The Limits tab shows the same transaction's query count against the 100 cap (governor limits explained).
Frequently asked questions
How do I fix "Too many SOQL queries: 101"?
Find the query firing in a loop (same query text and line number repeated in the log), move it outside the loop with an IN :ids filter, and use a Map for per-record lookup. Also rule out trigger recursion re-firing automation.
What makes a SOQL query non-selective?
Filters that can't use an index: non-indexed fields, leading-wildcard LIKE, negative operators, and null comparisons. On large objects this causes errors in trigger context and slowness everywhere.
How do I see SOQL queries in a debug log?
Set the Database category to FINEST; each query then logs SOQL_EXECUTE_BEGIN (query text, line number) and SOQL_EXECUTE_END (row count). ForceLens aggregates them automatically in its SOQL tab.
Does SOQL time count toward the CPU limit?
No — database time is excluded from the Apex CPU limit. But retrieved rows count toward the 50,000 query-row limit, and slow queries still slow the transaction.
See every query at a glance
Open a log in ForceLens and the SOQL tab shows each query's count, rows and origin — loop queries are unmissable. Free and local.