Fetching null records out of a function takes much longer than non-null
612987Mar 18 2009 — edited Mar 23 2009Hi,
We have a function that is called thousands of times on SQL. This function has a SELECT than can return one row at max.
We realized that when the SQL statement doesn't return any record, it takes 3x times longer in the fetch phase.
I made a simple test with three functions were each one was executed 1000 times. The first one has an extra outer join that guarantees that it always returns one record, a second one with an explicit cursor that can return 0 records and a third one with an implicit cursor that can also return 0 records.
Here is the sample test code:
DECLARE
-- Local variables here
CURSOR c IS
SELECT teste_vasco.teste_vasco1(epis.id_episode) as val
FROM episode epis
WHERE rownum <= 1000;
TYPE t_c IS TABLE OF c%ROWTYPE;
l_c t_c;
BEGIN
-- Test statements here
OPEN c;
FETCH c BULK COLLECT
INTO l_c;
CLOSE c;
for i in l_c.first..l_c.last
loop
dbms_output.put_line(i || ' :' || l_c(i).val);
end loop;
END;
The difference between the tests is that instead of calling the vasco1 function, vasco2 and vasco3 is called.
-----
###Test1
-Function vasco1:
FUNCTION teste_vasco1(i_episode IN episode.id_episode%TYPE) RETURN VARCHAR2 IS
l_dt_set TIMESTAMP WITH LOCAL TIME ZONE;
l_flg_stage VARCHAR2(3);
l_dt_warn TIMESTAMP WITH LOCAL TIME ZONE;
CURSOR c_care_stage IS
SELECT cs.dt_set, cs.flg_stage, cs.dt_warn
FROM episode epis
LEFT JOIN care_stage cs ON (cs.id_episode = epis.id_episode AND cs.flg_active = 'Y')
WHERE epis.id_episode = i_episode;
BEGIN
OPEN c_care_stage;
FETCH c_care_stage
INTO l_dt_set, l_flg_stage, l_dt_warn;
CLOSE c_care_stage;
IF l_dt_set IS NULL
THEN
RETURN NULL;
END IF;
RETURN l_dt_set || l_flg_stage || l_dt_warn;
EXCEPTION
WHEN OTHERS THEN
pk_alert_exceptions.raise_error(error_code_in => SQLCODE, text_in => SQLERRM);
pk_alert_exceptions.reset_error_state;
RETURN NULL;
END teste_vasco1;
-Trace file:
********************************************************************************
SELECT TESTE_VASCO.TESTE_VASCO1(EPIS.ID_EPISODE) AS VAL
FROM
EPISODE EPIS WHERE ROWNUM <= 1000
call count cpu elapsed disk query current rows
Parse 1 0.01 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 1 0.04 0.06 0 8 0 1000
total 3 0.06 0.07 0 8 0 1000
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 286 (recursive depth: 1)
Rows Row Source Operation
1000 COUNT STOPKEY (cr=8 pr=0 pw=0 time=2035 us)
1000 INDEX FAST FULL SCAN EPIS_EPISODE_INFO_UI (cr=8 pr=0 pw=0 time=1030 us)(object id 153741)
********************************************************************************
(...)
********************************************************************************
SELECT CS.DT_SET, CS.FLG_STAGE, CS.DT_WARN
FROM
EPISODE EPIS LEFT JOIN CARE_STAGE CS ON (CS.ID_EPISODE = EPIS.ID_EPISODE AND
CS.FLG_ACTIVE = 'Y') WHERE EPIS.ID_EPISODE = :B1
call count cpu elapsed disk query current rows
Parse 1 0.00 0.00 0 0 0 0
Execute 1000 0.07 0.05 0 0 0 0
Fetch 1000 0.01 0.02 0 4001 0 1000
total 2001 0.09 0.07 0 4001 0 1000
Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 286 (recursive depth: 2)
********************************************************************************
-----
###Test2
-Function vasco2:
FUNCTION teste_vasco2(i_episode IN episode.id_episode%TYPE) RETURN VARCHAR2 IS
l_dt_set TIMESTAMP WITH LOCAL TIME ZONE;
l_flg_stage VARCHAR2(3);
l_dt_warn TIMESTAMP WITH LOCAL TIME ZONE;
CURSOR c_care_stage IS
SELECT cs.dt_set, cs.flg_stage, cs.dt_warn
FROM care_stage cs
WHERE cs.id_episode = i_episode
AND cs.flg_active = 'Y';
BEGIN
OPEN c_care_stage;
FETCH c_care_stage
INTO l_dt_set, l_flg_stage, l_dt_warn;
IF c_care_stage%NOTFOUND
THEN
CLOSE c_care_stage;
RETURN NULL;
END IF;
CLOSE c_care_stage;
IF l_dt_set IS NULL
THEN
RETURN NULL;
END IF;
RETURN l_dt_set || l_flg_stage || l_dt_warn;
EXCEPTION
WHEN OTHERS THEN
pk_alert_exceptions.raise_error(error_code_in => SQLCODE, text_in => SQLERRM);
pk_alert_exceptions.reset_error_state;
RETURN NULL;
END teste_vasco2;
-Trace File:
********************************************************************************
SELECT TESTE_VASCO.TESTE_VASCO2(EPIS.ID_EPISODE) AS VAL
FROM
EPISODE EPIS WHERE ROWNUM <= 1000
call count cpu elapsed disk query current rows
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 1 0.00 0.27 0 8 0 1000
total 3 0.00 0.27 0 8 0 1000
Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 286 (recursive depth: 1)
Rows Row Source Operation
1000 COUNT STOPKEY (cr=8 pr=0 pw=0 time=2048 us)
1000 INDEX FAST FULL SCAN EPIS_EPISODE_INFO_UI (cr=8 pr=0 pw=0 time=1045 us)(object id 153741)
********************************************************************************
(...)
********************************************************************************
SELECT CS.DT_SET, CS.FLG_STAGE, CS.DT_WARN
FROM
CARE_STAGE CS WHERE CS.ID_EPISODE = :B1 AND CS.FLG_ACTIVE = 'Y'
call count cpu elapsed disk query current rows
Parse 1 0.00 0.00 0 0 0 0
Execute 1000 0.03 0.05 0 0 0 0
Fetch 1000 0.00 0.00 0 2001 0 1
total 2001 0.03 0.06 0 2001 0 1
Misses in library cache during parse: 0
Optimizer mode: ALL_ROWS
Parsing user id: 286 (recursive depth: 2)
Rows Row Source Operation
1 TABLE ACCESS BY INDEX ROWID CARE_STAGE (cr=2001 pr=0 pw=0 time=11082 us)
1 INDEX RANGE SCAN CS_EPIS_FACT_FST_I (cr=2000 pr=0 pw=0 time=7815 us)(object id 688168)
********************************************************************************
-----
###Test3
-Function vasco3
FUNCTION teste_vasco3(i_episode IN episode.id_episode%TYPE) RETURN VARCHAR2 IS
l_dt_set TIMESTAMP WITH LOCAL TIME ZONE;
l_flg_stage VARCHAR2(3);
l_dt_warn TIMESTAMP WITH LOCAL TIME ZONE;
BEGIN
BEGIN
SELECT cs.dt_set, cs.flg_stage, cs.dt_warn
INTO l_dt_set, l_flg_stage, l_dt_warn
FROM care_stage cs
WHERE cs.id_episode = i_episode
AND cs.flg_active = 'Y';
EXCEPTION
WHEN no_data_found THEN
RETURN NULL;
END;
IF l_dt_set IS NULL
THEN
RETURN NULL;
END IF;
RETURN l_dt_set || l_flg_stage || l_dt_warn;
EXCEPTION
WHEN OTHERS THEN
pk_alert_exceptions.raise_error(error_code_in => SQLCODE, text_in => SQLERRM);
pk_alert_exceptions.reset_error_state;
RETURN NULL;
END teste_vasco3;
-Trace file:
********************************************************************************
SELECT TESTE_VASCO.TESTE_VASCO3(EPIS.ID_EPISODE) AS VAL
FROM
EPISODE EPIS WHERE ROWNUM <= 1000
call count cpu elapsed disk query current rows
Parse 1 0.00 0.00 0 0 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 1 0.25 0.27 0 8 0 1000
total 3 0.25 0.27 0 8 0 1000
Misses in library cache during parse: 1
Optimizer mode: ALL_ROWS
Parsing user id: 286 (recursive depth: 1)
Rows Row Source Operation
1000 COUNT STOPKEY (cr=8 pr=0 pw=0 time=2033 us)
1000 INDEX FAST FULL SCAN EPIS_EPISODE_INFO_UI (cr=8 pr=0 pw=0 time=1031 us)(object id 153741)
********************************************************************************
(...)
********************************************************************************
SELECT CS.DT_SET, CS.FLG_STAGE, CS.DT_WARN
FROM
CARE_STAGE CS WHERE CS.ID_EPISODE = :B1 AND CS.FLG_ACTIVE = 'Y'
call count cpu elapsed disk query current rows
Parse 1 0.00 0.00 0 0 0 0
Execute 1000 0.07 0.06 0 0 0 0
Fetch 1000 0.00 0.00 0 2001 0 1
total 2001 0.07 0.06 0 2001 0 1
Misses in library cache during parse: 1
Misses in library cache during execute: 1
Optimizer mode: ALL_ROWS
Parsing user id: 286 (recursive depth: 2)
Rows Row Source Operation
1 TABLE ACCESS BY INDEX ROWID CARE_STAGE (cr=2001 pr=0 pw=0 time=11119 us)
1 INDEX RANGE SCAN CS_EPIS_FACT_FST_I (cr=2000 pr=0 pw=0 time=7951 us)(object id 688168)
********************************************************************************
-----
As you can see, in the first example the fetch time of the SELECT in the function vasco1 takes 0.02 seconds and 0.06 in the SELECT of the test script. This test returned 1000 non-null records.
In the tests 2 and 3, the fetch phase of the SELECT in the test script takes much more time - 0.27 seconds, despite the fetch of the SELECT in the functions (vasco2 and vasco3) took 0.00 seconds (as it only returned 1 row for the 1000 executions) and the only difference between them is the function that is called. Both test2 and test3 returned 999 null records and 1 non-null record.
How it's possible than a select null records takes much longer than selecting non-null records?
Hope you can understand the problem and thank you in advance for any help or suggestions.