PL/SQL30 min readBy Priyanshu Pandey

PL/SQL Performance Tuning: Beyond the Basics

Master PL/SQL performance tuning. Learn how to minimize Context Switches, utilize the RESULT_CACHE, and write highly optimized loops.

Performance Guide · PL/SQL Masterclass

PL/SQL Performance Tuning: Eliminating the Bottlenecks

Writing PL/SQL that works is easy. Writing PL/SQL that scales to process millions of rows requires a deep understanding of memory, caching, and the dreaded Context Switch.

30 min read📅August 7, 2026✍️Priyanshu Pandey📚PL/SQL Masterclass

The Context Switch Penalty

Oracle Database has two distinct engines:

  1. The SQL Engine: Executes queries (SELECT, INSERT).
  2. The PL/SQL Engine: Executes procedural code (IF/THEN, LOOP).

When you write a PL/SQL FOR loop that executes an INSERT statement inside the loop, the PL/SQL engine must hand the data over to the SQL engine, wait for the insert, and then take back control. This handover is called a Context Switch.

If you loop 1 million times, you cause 1 million context switches. This overhead will absolutely destroy your performance. The golden rule of PL/SQL tuning is: Minimize Context Switches.

Batching

Batching with BULK COLLECT and FORALL

To eliminate context switches, we send data to the SQL engine in massive batches.

The Bad Way (Row-by-Row = Slow-by-Slow)

Causes 10,000 context switches!
PL/SQL
FOR r IN (SELECT emp_id, salary FROM employees WHERE status = 'ACTIVE') LOOP
  UPDATE payroll SET bonus = r.salary * 0.1 WHERE emp_id = r.emp_id;
END LOOP;

The Fast Way (BULK Processing)

PL/SQL
DECLARE
  TYPE emp_list_t IS TABLE OF employees.emp_id%TYPE;
  TYPE sal_list_t IS TABLE OF employees.salary%TYPE;
  
  v_emps emp_list_t;
  v_sals sal_list_t;
BEGIN
  -- 1 Context switch to fetch all 10,000 rows into memory
  SELECT emp_id, salary BULK COLLECT INTO v_emps, v_sals 
  FROM employees WHERE status = 'ACTIVE';
  
  -- 1 Context switch to update all 10,000 rows
  FORALL i IN 1..v_emps.COUNT
    UPDATE payroll SET bonus = v_sals(i) * 0.1 WHERE emp_id = v_emps(i);
END;
/

By batching, we reduced 10,000 context switches down to just 2.

⚠️

Memory Exhaustion

Do not BULK COLLECT 5 million rows at once. You will crash the server's PGA memory. Always use the LIMIT clause to fetch chunks of 5,000 to 10,000 rows at a time inside a loop.

Function Caching (RESULT_CACHE)

If you have a function that calculates a complex tax rate based on the current year, and you call it 50,000 times in a report, it executes 50,000 times. But the answer is always the same!

By adding the RESULT_CACHE clause to the function definition, Oracle executes the function once, saves the answer in shared memory, and returns the cached answer for the remaining 49,999 calls.

PL/SQL
CREATE OR REPLACE FUNCTION get_tax_rate (p_year NUMBER) 
  RETURN NUMBER 
  RESULT_CACHE 
IS
  v_rate NUMBER;
BEGIN
  -- This expensive query only runs ONCE per year requested!
  SELECT complex_tax_calc INTO v_rate FROM tax_rules WHERE year = p_year;
  RETURN v_rate;
END;
/

If the tax_rules table is updated, Oracle automatically invalidates the cache and recalculates it on the next call.

Passing by Reference (NOCOPY)

When you pass an IN OUT parameter to a procedure, Oracle makes a complete copy of the variable in memory. If you are passing a massive collection containing 10,000 records, this copying process consumes huge amounts of PGA memory and CPU.

You can tell Oracle to pass the variable by reference (just a memory pointer) using the NOCOPY compiler hint.

PL/SQL
PROCEDURE process_massive_array (
  p_data IN OUT NOCOPY large_collection_type
) IS
BEGIN
  -- No memory is duplicated. We are modifying the original array directly.
  NULL;
END;

Common Gotchas

Important Gotchas

  • !

    NOCOPY is just a compiler hint. If your procedure throws an exception, standard IN OUT variables will roll back to their original state. NOCOPY variables will NOT roll back—they are permanently modified. Ensure you have proper exception handling.

  • !

    Do not use RESULT_CACHE on a function that queries a highly volatile OLTP table. The cache will constantly invalidate, and the overhead of managing the cache will actually make your function slower.

Key Takeaways

Key Takeaways

  • Never use Row-by-Row processing. Use BULK COLLECT and FORALL to eliminate context switches.
  • Protect your PGA memory by always using the LIMIT clause with BULK COLLECT.
  • Use RESULT_CACHE for deterministic functions to bypass expensive re-executions.
  • Pass massive collections to procedures using the NOCOPY hint.
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 →