I
I faced severe performance degradation in one of my APEX applications. After some time I identified issue with APEX_UTIL.GET_SESSION_ITEM call.
Let's create test table with 1M rows:
--DROP TABLE test_table;
CREATE TABLE test_table (
id NUMBER,
group_id NUMBER
);
--
INSERT INTO /*+ append */ test_table (id, group_id)
SELECT ROWNUM, MOD(ROWNUM, 5)
FROM DUAL
CONNECT BY LEVEL <= 1000000;
--
COMMIT;
Check table content:
SELECT group_id, COUNT(*) AS rows_ FROM test_table GROUP BY group_id ORDER BY 1;
Now we can simulate query with APEX_UTIL.GET_SESSION_ITEM function. For this test item existance doesn't matter. Let's pretend, we want to select rows for specific group_id based on APEX item value.
SELECT COUNT(*) AS rows_ -- 152 sec
FROM test_table t
WHERE t.group_id = NVL(APEX_UTIL.GET_SESSION_STATE('APEX_ITEM'), 0);
Any ideas why this is that slow?
It is because that function is called for every single row in that table. Here is a proof.
BEGIN
apex_test.get_item_calls := 0;
--
FOR c IN (
SELECT COUNT(*) AS rows_ -- 152 sec
FROM test_table t
WHERE t.group_id = NVL(apex_test.get_item('APEX_ITEM'), 0)
) LOOP
DBMS_OUTPUT.PUT_LINE(c.rows_);
END LOOP;
--
DBMS_OUTPUT.PUT_LINE(apex_test.get_item_calls);
END;
/
Oh yes, you will need APEX_TEST wrapper:
CREATE OR REPLACE PACKAGE apex_test AS
get_item_calls NUMBER;
FUNCTION get_item (
in_name VARCHAR2
)
RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY apex_test AS
FUNCTION get_item (
in_name VARCHAR2
)
RETURN VARCHAR2 AS
BEGIN
get_item_calls := get_item_calls + 1; -- track calls
--
RETURN APEX_UTIL.GET_SESSION_STATE(in_name);
END;
END;
/
Now when we know what is wrong, we can fix it. WITH and hints to the rescue.
BEGIN
apex_test.get_item_calls := 0;
--
FOR c IN (
WITH x AS (
SELECT /*+ materialize */
--SELECT /*+ result_cache */
apex_test.get_item('APEX_ITEM') AS APEX_ITEM
FROM DUAL
)
SELECT COUNT(*) AS rows_ -- 152 sec
FROM test_table t
CROSS JOIN x
WHERE t.group_id = NVL(x.APEX_ITEM, 0)
) LOOP
DBMS_OUTPUT.PUT_LINE(c.rows_);
END LOOP;
--
DBMS_OUTPUT.PUT_LINE(apex_test.get_item_calls);
END;
/
You can use either (undocumented) materialize hint, which will call the function just once for every run. Or you can use result_cache hint, which will call that function twice for the first run and then most likely zero times for subsequent calls.
Learn more about hints.
Cleanup:
DROP TABLE test_table PURGE; DROP PACKAGE apex_test;
Comments
Post a Comment