Fix Guide

How to Fix Salesforce SOQL Query Timeout Errors

Step-by-step fix guide with AI-powered diagnosis from BuildForce.

Salesforce throws QUERY_TIMEOUT when SOQL execution exceeds 120 seconds (sync) or 200,000 records (without filters). The query optimizer determines selectivity based on the leading column's distinct value count — a filter is 'selective' only if the matching rows are below the selectivity threshold (10% standard, 5% for high-volume tables, 1M rows hard cap for full table scans). The fix: identify the leading filter column, ensure it's indexed (standard ID fields, External ID fields, or a custom Salesforce-provided index), avoid leading wildcards in LIKE clauses, replace OR with multiple filtered queries, and migrate large queries to Batch Apex or async patterns.

Selectivity ThresholdCustom IndexSkinny TableBatch Apex

Symptoms

QUERY_TIMEOUT error on a query that runs fine in smaller orgs

SOQL on a custom field returning 'non-selective query against large object type'

Reports timing out for the same query that worked last quarter

Apex test running fine in dev but timing out in production

List view filters running for 30+ seconds before displaying

Bulk API extract job aborting at 90+ minutes with no records returned

Root Causes

1

Filter column not indexed

Salesforce only uses indexed columns for selectivity. If your query's leading WHERE filter is on a non-indexed text field, the optimizer must full-scan the table. For tables over 200,000 records, this guarantees a timeout.

2

Filter not actually selective despite being indexed

Even an indexed field is non-selective if the matching value applies to >10% of rows (5% for tables >1M). 'Status = Active' on a table where 80% of rows are Active is non-selective regardless of index status.

3

Leading wildcard in LIKE clause

LIKE '%foo' prevents index usage entirely — Salesforce must scan every row. LIKE 'foo%' can use the index. Always anchor LIKE patterns at the start to maintain selectivity.

4

OR clauses preventing optimizer from choosing one index

WHERE A = 'x' OR B = 'y' forces the optimizer to either union two index lookups (often slow) or full-scan. Splitting into two queries with a UNION in Apex code is usually faster than one OR query.

5

Large data volume on a table without skinny table or custom index

Tables with 5M+ rows need either a skinny table (Salesforce-managed denormalized copy with the most-queried columns) or a custom index requested via support. Standard indexes can't handle very large tables for complex multi-column filters.

How to Fix It — Step by Step

1

Run the query with EXPLAIN via the Query Plan tool

In Developer Console, run your SOQL with the Query Plan checkbox enabled. The output shows leading column, cost, and table scan vs index. A 'Table Scan' on a table over 200k rows is your timeout source.

Example
// Developer Console → Query Editor → Enable Query Plan
SELECT Id, Name FROM Account WHERE Industry = 'Technology' AND BillingState = 'CA'
2

Identify the most selective filter column

Run a count query on each filter column separately. The column whose filter narrows to <1% of total rows is your selectivity winner. Reorder your WHERE clause to put that filter first (Salesforce's optimizer mostly handles this, but explicit ordering helps in older API versions).

Example
SELECT COUNT() FROM Account WHERE Industry = 'Technology'
// vs
SELECT COUNT() FROM Account WHERE BillingState = 'CA'
3

Add a custom index via Salesforce support

If a frequently-filtered column isn't indexed by default (not an ID, External ID, or auto-indexed by Salesforce), open a support case requesting a custom index. Indicate the field's distinct value count and selectivity expectations.

4

Replace leading wildcards and OR with UNION patterns

Rewrite LIKE '%foo' to use a custom index or full-text search instead. Replace OR queries with two indexed queries and union the results in Apex. The combined result is typically 5-10x faster than a single OR query on large tables.

Example
// BEFORE — slow OR
List<Account> accs = [SELECT Id FROM Account WHERE Industry = 'Tech' OR BillingState = 'CA'];

// AFTER — two indexed queries
Map<Id, Account> result = new Map<Id, Account>();
for (Account a : [SELECT Id FROM Account WHERE Industry = 'Tech']) result.put(a.Id, a);
for (Account a : [SELECT Id FROM Account WHERE BillingState = 'CA']) result.put(a.Id, a);
5

Request a skinny table for very large objects

For tables >5M rows that are frequently queried with the same column set, request a skinny table from Salesforce support. Skinny tables denormalize the most-queried columns into a separate physical table, dramatically reducing query time. Available on Enterprise+ editions.

6

Migrate large extracts to Batch Apex

Synchronous SOQL caps at 50,000 rows per query. Batch Apex processes records in chunks (default 200) and runs in the background. For extracts of 1M+ records, this is the only viable pattern.

Example
public class LargeExtractBatch implements Database.Batchable<sObject> {
  public Database.QueryLocator start(Database.BatchableContext ctx) {
    return Database.getQueryLocator('SELECT Id, Name FROM Account WHERE LastModifiedDate >= LAST_N_DAYS:30');
  }
  public void execute(Database.BatchableContext ctx, List<Account> scope) {
    // process scope
  }
  public void finish(Database.BatchableContext ctx) {}
}
Database.executeBatch(new LargeExtractBatch(), 2000);
7

Use the Bulk API 2.0 query endpoint for one-time large extracts

For one-time data extracts of millions of records, Bulk API 2.0 query jobs run server-side without the Apex governor limits. The result is downloaded as a CSV chunked into manageable batches.

Example
POST /services/data/v59.0/jobs/query
{
  "operation": "query",
  "query": "SELECT Id, Name, Email FROM Contact WHERE LastModifiedDate >= LAST_N_DAYS:30"
}

Let BuildForce diagnose and fix this automatically

Instead of following manual steps, connect your org and let our AI identify exactly what's broken and how to fix it — in minutes.

Book a Demo

Common Questions

More answers about this issue and how to resolve it.

Stop debugging manually. Let AI do it.

BuildForce runs 200+ automated checks across your Salesforce org and tells you exactly what's broken and how to fix it.