Почему Oracle 12.1.0.2 пропускает вызовы функций в таблицах с кэшированием результатов? - PullRequest
0 голосов
/ 02 мая 2018

Я сократил реальную проблему до следующего контрольного примера:

DROP TABLE test_users;
CREATE TABLE test_users (
  user_id INTEGER,
  username VARCHAR2(32),
  first_name VARCHAR2(40),
  last_name VARCHAR2(40)
);
ALTER TABLE test_users ADD
(
  CONSTRAINT test_users_pk 
  PRIMARY KEY (user_id)
  USING INDEX
)
/
ALTER TABLE test_users ADD
(
  CONSTRAINT test_users_uq 
  UNIQUE (username)
  USING INDEX
)
/

INSERT INTO test_users VALUES (1, 'A', 'Sneezy', 'Timon');
INSERT INTO test_users VALUES (2, 'B', 'Dopey', 'Simba');
INSERT INTO test_users VALUES (3, 'C', 'Happy', 'Nala');
INSERT INTO test_users VALUES (4, 'D', 'Grumpy', 'Pumbaa');
COMMIT;
CREATE OR REPLACE FUNCTION test_function RETURN test_users.user_id%TYPE IS
    identifier VARCHAR2(32);
    user_id    users.user_id%TYPE;
  BEGIN
    SELECT sys_context('userenv', 'client_identifier') INTO identifier FROM dual;

    SELECT user_id INTO user_id FROM test_users WHERE upper(username) = upper(identifier);
    dbms_output.put_line('TEST_FUNCTION called!');
    RETURN user_id;

END test_function;

-- Testing with disabled result cache
ALTER TABLE test_users RESULT_CACHE (MODE DEFAULT);
DECLARE
  f users.first_name%TYPE;
  last_name  users.last_name%TYPE;
  identifier VARCHAR2(32);
  l_user_id users.user_id%type;
BEGIN
  dbms_output.put_line('setting the session identifier to A (Sneezy, Timon):');
  dbms_session.set_identifier('A');
  l_user_id := test_function();
  dbms_output.put_line('function call in WHERE criteria:');
  SELECT first_name, last_name INTO f, last_name FROM test_users WHERE user_id = test_function();
  dbms_output.put_line(f || ' ' || last_name);
  dbms_output.put_line('variable use in WHERE criteria:');
  SELECT first_name, last_name INTO f, last_name FROM test_users WHERE user_id = l_user_id;
  dbms_output.put_line(f || ' ' || last_name);
  dbms_output.put_line('----');
  dbms_output.put_line('setting the session identifier to B (Dopey Simba):');
  dbms_session.set_identifier('B');
  l_user_id := test_function();
  dbms_output.put_line('function call in WHERE criteria:');
  SELECT first_name, last_name INTO f, last_name FROM test_users WHERE user_id = test_function();
  dbms_output.put_line(f || ' ' || last_name);
  dbms_output.put_line('variable use in WHERE criteria:');
  SELECT first_name, last_name INTO f, last_name FROM test_users WHERE user_id = l_user_id;
  dbms_output.put_line(f || ' ' || last_name);

END;
/

-- Testing with enabled result cache
ALTER TABLE test_users RESULT_CACHE (MODE FORCE);
DECLARE
  f users.first_name%TYPE;
  last_name  users.last_name%TYPE;
  identifier VARCHAR2(32);
  l_user_id users.user_id%type;
BEGIN
  dbms_output.put_line('setting the session identifier to A (Sneezy, Timon):');
  dbms_session.set_identifier('A');
  l_user_id := test_function();
  dbms_output.put_line('function call in WHERE criteria:');
  SELECT first_name, last_name INTO f, last_name FROM test_users WHERE user_id = test_function();
  dbms_output.put_line(f || ' ' || last_name);
  dbms_output.put_line('variable use in WHERE criteria:');
  SELECT first_name, last_name INTO f, last_name FROM test_users WHERE user_id = l_user_id;
  dbms_output.put_line(f || ' ' || last_name);
  dbms_output.put_line('----');
  dbms_output.put_line('setting the session identifier to B (Dopey Simba):');
  dbms_session.set_identifier('B');
  l_user_id := test_function();
  dbms_output.put_line('function call in WHERE criteria:');
  SELECT first_name, last_name INTO f, last_name FROM test_users WHERE user_id = test_function();
  dbms_output.put_line(f || ' ' || last_name);
  dbms_output.put_line('variable use in WHERE criteria:');
  SELECT first_name, last_name INTO f, last_name FROM test_users WHERE user_id = l_user_id;
  dbms_output.put_line(f || ' ' || last_name);

END;
/

Индексы могут быть или не быть необходимыми. Идея в том, что имя текущего пользователя находится в идентификаторе сеанса. Функция теста превращает имя пользователя в идентификаторе сеанса в идентификатор пользователя. Имена пользователей могут (теоретически) меняться и использоваться как имена для входа. Идентификатор пользователя никогда не должен изменяться и, следовательно, является PK таблицы.

Что меня беспокоит, так это то, что при включении кэша результатов функция в критериях WHERE этого оператора не всегда вызывается:

SELECT first_name, last_name INTO f, last_name FROM test_users WHERE user_id = test_function();

Первый блок PL / SQL дает такой результат:

setting the session identifier to A (Sneezy, Timon):
TEST_FUNCTION called!
function call in WHERE criteria:
TEST_FUNCTION called!
Sneezy Timon
variable use in WHERE criteria:
Sneezy Timon
----
setting the session identifier to B (Dopey Simba):
TEST_FUNCTION called!
function call in WHERE criteria:
TEST_FUNCTION called!
Dopey Simba
variable use in WHERE criteria:
Dopey Simba

Второй блок производит это:

setting the session identifier to A (Sneezy, Timon):
TEST_FUNCTION called!
function call in WHERE criteria:
TEST_FUNCTION called!
Sneezy Timon
variable use in WHERE criteria:
Sneezy Timon
----
setting the session identifier to B (Dopey Simba):
TEST_FUNCTION called!
function call in WHERE criteria:
Sneezy Timon
variable use in WHERE criteria:
Dopey Simba

Как вы можете заметить, TEST_FUNCTION вызывает на один вызов меньше и имеет неправильный результат. Как я понял, кеширование результатов, таблица пользователей должна быть идеальным кандидатом. Много SELECT, очень мало DML. И все работает как надо, если я не поместил свой вызов функции в критерии WHERE. Если я вызываю функцию, сохраняю результат в переменной и использую его в критериях WHERE, все в порядке.

Почему это? Это ошибка или особенность? Является ли тот факт, что функция использует данные из идентификатора сеанса, основной проблемой? Или кеш результатов, как правило, не должен быть включен для всей таблицы?

Edit: Прочитав некоторые ответы, я попытался явно объявить функцию как кэшированный результат, например:

CREATE OR REPLACE FUNCTION test_function(identifier VARCHAR2 DEFAULT sys_context('userenv', 'client_identifier'))
    RETURN test_users.user_id%TYPE result_cache relies_on(test_users) IS
    user_id test_users.user_id%TYPE;
BEGIN
    SELECT user_id INTO user_id FROM test_users WHERE upper(username) = upper(identifier);
    dbms_output.put_line('TEST_FUNCTION called!');
    RETURN user_id;
END test_function;

Это очень похоже на пример из документации Oracle в комментарии ниже.

К сожалению, это не помогло. Вызов функции с паратезами или без них не имеет значения для меня (но см. Мой комментарий ниже). Единственный способ найти ожидаемые результаты - отключить кеш результатов для таблицы.

Ответы [ 3 ]

0 голосов
/ 02 мая 2018

Проблема не в кэшировании вашей таблицы TEST_USERS. Проблема в том, что результаты вашей функции TEST_FUNCTION кэшируются, и изменение идентификатора сеанса не делает эти результаты недействительными.

Чтобы избежать этой проблемы, сначала измените определение TEST_FUNCTION на следующее:

CREATE OR REPLACE FUNCTION test_function ( identifier VARCHAR2 DEFAULT sys_context('userenv', 'client_identifier') ) RETURN test_users.user_id%TYPE IS
    --identifier VARCHAR2(32);
    user_id    test_users.user_id%TYPE;
  BEGIN
    --SELECT sys_context('userenv', 'client_identifier') INTO identifier FROM dual;

    SELECT user_id INTO user_id FROM test_users WHERE upper(username) = upper(identifier);
    dbms_output.put_line('TEST_FUNCTION called!');
    RETURN user_id;

END test_function;

Затем, когда вы используете его в предложении WHERE, назовите его так:

SELECT first_name, last_name 
INTO f, last_name 
FROM test_users WHERE user_id = test_function;

Важно: обратите внимание, что я не использовал test_function() (т.е. обратите внимание, что в скобках нет).

Почему скобки имеют значение? Я не знаю. Я не думаю, что они должны. Но это работает в моем экземпляре 12.1.0.2.

Запустив вариант тестового примера, который вы выложили, я получаю эти результаты в конце:

function call in WHERE criteria with no parens...: Dopey Simba
function call in WHERE criteria with parens...: Sneezy Timon
0 голосов
/ 02 мая 2018
SELECT 
        S.ST_NM, 
        FPS.PRTCPNT_TYP_CD,
        FPS.IVD_UNQ_PRSN_CNT IVD,
        FPS.NIVD_UNQ_PRSN_CNT NonIVD,
        FPS.UNQ_PRSN_CNT Total,
        SUM(CASE WHEN FBPS.CASE_TYP_CD = 'F' AND FBPS.ST_CD <> 'ZZ000' THEN FBPS.FV_ADD_CNT ELSE 0 END) IVDAdded,
        SUM(CASE WHEN FBPS.CASE_TYP_CD = 'F' AND FBPS.ST_CD <> 'ZZ000' THEN FBPS.FV_RMVL_CNT ELSE 0 END) IVDRemoved,
        SUM(CASE WHEN FBPS.CASE_TYP_CD = 'F' AND FBPS.ST_CD <> 'ZZ000' THEN FBPS.FV_ADD_CNT - FBPS.FV_RMVL_CNT ELSE 0 END) IVDNet,
        SUM(CASE WHEN FBPS.CASE_TYP_CD = 'N' AND FBPS.ST_CD <> 'ZZ000' THEN FBPS.FV_ADD_CNT ELSE 0 END) NonIVDAdded,
        SUM(CASE WHEN FBPS.CASE_TYP_CD = 'N' AND FBPS.ST_CD <> 'ZZ000' THEN FBPS.FV_RMVL_CNT ELSE 0 END) NonIVD Removed,
        SUM(CASE WHEN FBPS.CASE_TYP_CD = 'N' AND FBPS.ST_CD <> 'ZZ000' THEN FBPS.FV_ADD_CNT - FBPS.FV_RMVL_CNT ELSE 0 END) NonIVDNet
from
        STATE  S
        LEFT JOIN FCR_PRSN_SUMRY FPS ON FPS.PRTCPNT_TYP_CD IN ('CH','CP','NP','PF','ZZ')
                    and FPS.FV_IND = 'Y' 
                    AND FPS.SERIES_CD = 'FV002'
                    AND FPS.ST_CD = S.ST_CD 
        LEFT JOIN FCR_BATCH_PRSN_SUMRY FBPS ON FBPS.ST_CD = FPS.ST_CD AND FBPS.PRCSD_DT = FPS.PRCSD_DT
        LEFT JOIN MI_CALENDAR MI ON MI.PRCSD_DT = FPS.PRCSD_DT  AND PREV_CAL_MTH_STRT_DT  = :START_DATE
                                                                AND PREV_CAL_MTH_END_DT    = :END_DATE
WHERE S.ST_CD IN (SELECT ST_CD 
                             FROM ADMN_ST_GRP 
                             WHERE ST_GRP_ID = 'STDZZ')                                                             
GROUP BY        
        S.ST_NM, FPS.PRTCPNT_TYP_CD,
        FPS.IVD_UNQ_PRSN_CNT,
        FPS.NIVD_UNQ_PRSN_CNT,
        FPS.UNQ_PRSN_CNT
ORDER BY S.ST_NM;       
0 голосов
/ 02 мая 2018

Похоже, что Oracle видит оба вызова

SELECT first_name, last_name INTO f, last_name FROM test_users WHERE user_id = test_function();

и учитывая, что запросы выглядят одинаково, он может повторно использовать результат первого вызова во втором. Поскольку test_function() не является детерминированным, это предположение неверно (возможно, это ошибка, Oracle не должна предполагать, что функции являются детерминированными, если в объявлении функции не используется предложение DETERMINISTIC).

Когда вы используете явные привязки параметров, Oracle понимает, что не только запросы должны быть эквивалентными, но и значения параметров должны быть одинаковыми, чтобы кэш результатов был действительным. Поэтому написание ваших запросов с явными параметрами может быть приемлемым вариантом, если у вас нет дополнительных ограничений.

Во всяком случае, я лично не стал бы беспокоиться о кеше результатов, запросы слишком просты; вы могли бы полагаться на старый добрый буферный кеш для ускорения этих запросов, при условии, что у вас есть индекс в USER_ID, который очень , вероятно, имеет место (в конце концов, вы говорите, что это PK). Кэш результатов будет более полезным, поскольку запрос становится более сложным, потому что в этих случаях Oracle сможет обойти большие поддеревья плана выполнения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...