我将现实生活中的问题减少到以下测试用例:
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;
/
索引可能是必要的,也可能不是必需的。这里的想法是当前用户的名字在会话标识符中。测试功能将会话标识符中的用户名转换为用户ID。用户名可以(理论上)更改,并用作登录名。用户ID永远不会改变,因此是表的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条件中使用它,一切都很好。
为什么?这是一个错误还是一个功能?事实上该函数使用来自会话标识符的数据是主要问题吗?或者通常不会为整个表打开结果缓存吗?
编辑: 在阅读了一些答案之后,我尝试明确地将该函数声明为缓存结果,如下所示:
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文档示例非常相似。
可悲的是,这没有用。使用或不使用parantheses调用函数对我没有任何影响(但请参阅下面的评论)。我发现始终获得预期结果的唯一方法是禁用表的结果缓存。
答案 0 :(得分:1)
问题不在于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
答案 1 :(得分:0)
似乎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将能够绕过执行计划的更大子树。
答案 2 :(得分:-3)
.handle()