How I Reduced My Oracle SQL Execution Time from 110s to 2s

How I Reduced My Oracle SQL Execution Time from 110s to 2s




🧩 The Problem

Recently, I encountered a performance bottleneck while writing a SQL query: the execution time was over 110 seconds. Here’s a simplified version of the query:

SELECT
  'A' AS type,
  COUNT(adl.is_attend) AS count,
  ROUND(SUM(CASE WHEN adl.is_attend = 1 THEN 1 ELSE 0 END) / COUNT(adl.is_attend), 2) * 100 AS rate
FROM
  attendance adl
  INNER JOIN (
    SELECT deptId
    FROM department
    START WITH deptId = '...'
    CONNECT BY up_daptId = PRIOR deptId
  ) nbd ON adl.deptId = nbd.deptId
WHERE 
  TO_DATE(adl.date, 'yyyy-MM-dd') >= TO_DATE(#{begin_date}, 'yyyy-MM-dd')
  AND TO_DATE(adl.date, 'yyyy-MM-dd') <= TO_DATE(#{end_date}, 'yyyy-MM-dd')

UNION ALL

-- other queries with different deptId and type...

Enter fullscreen mode

Exit fullscreen mode

The rest of the query consists of several almost identical blocks (differing only in deptId and type) combined with UNION ALL.

The backend dynamically passes #{begin_date} and #{end_date} as query parameters.



⌛ Performance Issue

  • Each individual SQL block took ~18 seconds to run.
  • With 6 blocks in total, the final query time reached over 110 seconds.
  • The attendance table contains 13+ million records, and even simple queries take around 20s.



Optimization Process

Let’s break down the root causes and how I tackled them.



Root Cause 1: Data Volume

  • The attendance table is massive. That’s unavoidable.
  • Filtering by date helps, but it’s not enough.



Root Cause 2: Recursive Join

  • The department table is joined using a recursive hierarchical query (CONNECT BY) to fetch all sub-departments.
  • Recursive joins are inherently expensive, especially without indexes.



✅ Solution 1: Index Optimization on deptId

The most effective optimization was to add an index on deptId:

CREATE INDEX idx_deptId ON department(deptId);
Enter fullscreen mode

Exit fullscreen mode

Result: Execution time dropped from 18s to 2s for each block.
This alone brought the total query time down dramatically.



✅ Solution 2 (Optional): Avoiding TO_DATE() in WHERE Clause

Initially, we used TO_DATE() for date comparison:

TO_DATE(adl.date, 'yyyy-MM-dd') >= TO_DATE(#{begin_date}, 'yyyy-MM-dd')
Enter fullscreen mode

Exit fullscreen mode

However, calling functions on table columns prevents Oracle from using indexes. One workaround is to compare strings directly, as Oracle can implicitly convert strings to dates based on session settings:

adl.date >= #{begin_date} AND adl.date <= #{end_date}
Enter fullscreen mode

Exit fullscreen mode

This further reduced execution time from 2s to ~1s per block.

But beware: Implicit conversions depend on the NLS_DATE_FORMAT session setting. This can lead to:

  • Loss of precision (e.g., minutes and seconds)
  • Wrong century (due to two-digit years)
  • Inconsistent behavior across environments



My Decision: Keep TO_DATE()

To ensure correctness and avoid unexpected results, I chose not to rely on implicit conversion and retained TO_DATE() in production code, even if it’s slightly slower.



🧠 Key Takeaways

  • Always index your join keys, especially in recursive joins.
  • Avoid applying functions directly on indexed columns in WHERE clauses.
  • Be cautious with implicit type conversions — they can be a double-edged sword.
  • Performance tuning is often about small, thoughtful changes.



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *