SQL24 min readBy Priyanshu Pandey

SQL Performance: Mastering Execution Plans & The Cost-Based Optimizer (CBO)

A definitive guide to SQL Performance Tuning. Master the Oracle Cost-Based Optimizer (CBO), read EXPLAIN PLANs, analyze access paths (Full Table Scans, Index Range Scans), understand join methods (Nested Loops, Hash Joins), and learn how to rewrite slow queries using real-world AWR/ASH metrics.

Performance · SQL Mastery Series

Mastering Execution Plans: The Cost-Based Optimizer & SQL Tuning

Writing SQL that works is easy. Writing SQL that scales to billions of rows is an art. Dive deep into the Oracle Cost-Based Optimizer (CBO), decode complex EXPLAIN PLANs, and learn how to fundamentally rewrite slow queries based on access paths and join methodologies.

24 min read📅June 25, 2026✍️Priyanshu Pandey📚SQL Mastery Series
THE OPTIMIZER

1. The Cost-Based Optimizer (CBO)

When you submit a SQL query, the database doesn't just blindly execute it from top to bottom. It passes through the Optimizer — a highly sophisticated engine that analyzes the query, looks at the database statistics, evaluates thousands of possible execution paths, and chooses the one with the lowest "cost".

The Parsing Lifecycle

  1. Syntax Check: Is the SQL valid?
  2. Semantic Check: Do the tables and columns exist? Do you have privileges?
  3. Shared Pool Check (Soft Parse): Has this exact query (with the exact same bind variables) been run before? If so, reuse the cached execution plan.
  4. Optimization (Hard Parse): If not, evaluate all possible paths, estimate rows (cardinality), estimate cost, and generate a new Execution Plan.
⚠️

Hard Parsing is Expensive

A hard parse requires CPU cycles and latches (locks) on the data dictionary. If an application constantly hard parses queries because it uses literal values (WHERE status = 'A') instead of bind variables (WHERE status = :b1), it will choke the database CPU. Always use bind variables in OLTP applications.


2. Reading an Execution Plan

An execution plan is a tree of operations. The most reliable way to get an accurate execution plan for a query you just ran is using DBMS_XPLAN.DISPLAY_CURSOR.

Generating an Execution Plan
SQL
-- 1. Run your query with the GATHER_PLAN_STATISTICS hint
SELECT /*+ GATHER_PLAN_STATISTICS */ 
       d.department_name, e.last_name
  FROM employees e
  JOIN departments d ON e.department_id = d.department_id
 WHERE e.salary > 10000;

-- 2. Fetch the plan immediately after
SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY_CURSOR(
    sql_id => NULL, 
    cursor_child_no => NULL, 
    format => 'ALLSTATS LAST'
));

Understanding the Plan Output

The output of DBMS_XPLAN looks like a hierarchical tree.

  • Id: The step number.
  • Operation: What the database is doing (e.g., HASH JOIN, TABLE ACCESS FULL).
  • Name: The object being accessed.
  • Starts: How many times this step was executed.
  • E-Rows (Estimated Rows): What the CBO thought it would find.
  • A-Rows (Actual Rows): What it actually found. (If E-Rows and A-Rows are wildly different, your statistics are stale!)

How to read it: Start at the most indented operations first. Sibling operations (same indentation) are executed top-to-bottom. Data flows up the tree.

ACCESS PATHS

3. Data Access Paths

How the database retrieves your data from disk dictates a massive portion of the query's total cost.

Full Table Scan (FTS)

The database reads every single block of the table below the High Water Mark (HWM).

  • When it's good: When you are fetching a large percentage of the table (typically > 10%). It uses multi-block reads, making it highly efficient for massive data extraction.
  • When it's bad: When you only want 5 rows out of a 100-million-row table.

Index Range Scan

The database traverses the B-Tree index structure to find the leaf blocks containing the rowids, then performs a "Table Access By Index Rowid" to fetch the actual data block.

  • When it's good: When fetching highly selective data (e.g., < 5% of the table).
  • When it's bad: When fetching too much data. Single-block reads are slower than multi-block reads. If an Index Range Scan fetches 50% of the table, the constant hopping between the index and the table blocks will be catastrophically slow.

Index Fast Full Scan

The database reads all the blocks in the index, completely ignoring the B-Tree structure, using multi-block reads. It does not touch the table.

  • When it happens: When your SELECT, WHERE, and JOIN clauses can be satisfied entirely by the columns in the index.
💡

Covering Indexes

If you have a slow query, consider creating a "Covering Index" — an index that contains all the columns the query needs. The optimizer will use an Index Fast Full Scan, completely avoiding the expensive Table Access step.

JOIN METHODS

4. Join Methodologies

When joining two tables, the CBO must choose a physical join strategy. Understanding these is the key to advanced SQL tuning.

Nested Loops Join

The database takes the first table (the "driving" table), and for every single row found, it performs a lookup in the second table (the "inner" table).

  • Mechanism: For each row in Table A -> Lookup match in Table B.
  • Ideal for: Joining small datasets.
  • Requirement: The inner table must have an index on the join column. If there's no index, the database will perform a Full Table Scan on the inner table for every row in the driving table, causing the query to hang indefinitely.

Hash Join

The database takes the smaller of the two tables, builds a hash table in memory (PGA) using the join key, and then scans the larger table, probing the hash table for matches.

  • Mechanism: Build Hash Table(Table A) -> Scan Table B -> Hash(Key) -> Match?
  • Ideal for: Joining large datasets where an index is not effective or doesn't exist.
  • Requirement: Only works for equi-joins (=). Requires sufficient memory (PGA) or it will spill to the temporary tablespace (disk), drastically slowing down.

Sort Merge Join

The database sorts both tables by the join key, and then merges them together by walking through both sorted lists simultaneously.

  • Ideal for: Non-equi joins (>, <, BETWEEN) where Hash Joins cannot be used, or when the data is already sorted by an index.
Identifying Joins in a Plan
SQL
-------------------------------------------------------------------------
| Id  | Operation           | Name        | Starts | A-Rows |   A-Time   |
-------------------------------------------------------------------------
|   0 | SELECT STATEMENT    |             |      1 |     15 |00:00:00.05 |
|*  1 |  HASH JOIN          |             |      1 |     15 |00:00:00.05 |
|   2 |   TABLE ACCESS FULL | DEPARTMENTS |      1 |     27 |00:00:00.01 |
|*  3 |   TABLE ACCESS FULL | EMPLOYEES   |      1 |    107 |00:00:00.01 |
-------------------------------------------------------------------------
REAL WORLD TUNING

5. The Tuning Workflow

When presented with a slow query, follow this methodology:

  1. Check the Cardinality Estimates: Look at E-Rows vs A-Rows in DBMS_XPLAN. If the CBO expects 1 row but gets 1,000,000, it chose the wrong plan (probably a Nested Loop instead of a Hash Join). Fix: Gather table statistics.
  2. Look for Bad Nested Loops: A Nested Loop where the "Starts" on the inner table is in the millions is a disaster. Fix: Add a missing index on the join column, or use a /*+ USE_HASH(a b) */ hint to force a Hash Join.
  3. Look for Unnecessary Full Table Scans: Is a 500GB table being fully scanned to return 10 rows? Fix: Add an index on the WHERE clause columns.
  4. Identify Implicit Conversions: WHERE TO_CHAR(order_date) = '2025-01-01' bypasses the index on order_date. Fix: Rewrite to WHERE order_date = DATE '2025-01-01' or create a Function-Based Index.

Key Takeaways

    RetailCoder
    All systems operational
    v1.0 Live

    RC:OMS

    Multi-channel order management with double-entry inventory ledger. Amazon, Flipkart, Shopify, WooCommerce — one source of truth.

    Launch demo →
    v1.0 Live

    RC:Storefront

    Self-hosted headless e-commerce. Your server, your data, zero transaction fees. Native RC:OMS inventory sync.

    Visit Storefront →
    Pipeline

    RC:Pulse

    AI-powered retail analytics and demand forecasting — built natively on top of your RC:OMS and Storefront data.

    Request early access →
    Built in India 🇮🇳  ·  Architected by Priyanshu PandeyTalk to an engineer →