我有以下查询(这是一个更复杂的查询的简化版本):
SELECT * FROM TPM_TASK
WHERE (PROJECTID, VERSIONID) IN ((3,1), (24,1), (4,1))
在代码中,我将构建
(PROJECTID,VERSIONID)
以编程方式列出 key 列表,这个列表可能有几千对长。我的问题是 Oracle 将如何优化此查询,因为
ProjectId
和 VersionId
被索引。将列表转换为哈希表,类似于 join
反对临时表?或者每个键查找一次完成一个?我在我的测试数据库下尝试了这个查询并得到:
SELECT STATEMENT 68.0 68 2989732 19 8759 68 ALL_ROWS
TABLE ACCESS (FULL) 68.0 68 2989732 19 8759 1 TPMDBO TPM_TASK FULL TABLE ANALYZED 1
但是,我相信这个数据库没有足够的数据来保证索引扫描。我尝试了关于生产的查询并得到:
SELECT STATEMENT 19.0 19 230367 23 9683 19 ALL_ROWS
INLIST ITERATOR 1
TABLE ACCESS (BY INDEX ROWID) 19.0 19 230367 23 9683 1 TPMDBO TPM_TASK BY INDEX ROWID TABLE ANALYZED 1
INDEX (RANGE SCAN) 4.0 4 64457 29 1 TPMDBO TPM_H1_TASK RANGE SCAN INDEX ANALYZED 1
这似乎达到了索引,但是我不确定 INLIST ITERATOR 是什么意思。我猜这意味着 Oracle 正在遍历列表并对列表中的每个项目进行表访问,这对于数千个键可能不会太有效。但是,如果我确实给了它几千个 key ,也许 Oracle 足够聪明,可以更好地优化它。
注意:我不想将这些键加载到临时表中,因为坦率地说,我不喜欢临时表在 Oracle 下的工作方式,而且它们通常最终会比它们的值(value)更令人沮丧(无论如何,在我的非专家意见中。 )
最佳答案
优化器应根据列表中的项目数和表中的行数做出决定。如果表有数百万行,而列表甚至有几千个项目,我通常希望它会使用索引来进行几千个单行查找。如果表有几千行并且列表有几千个项目,我希望优化器对表进行全面扫描。当然,中间是所有有趣的事情发生的地方,并且更难准确地计算出优化器将选择的计划。
然而,一般来说,从性能的角度来看,动态构建这种查询将是有问题的,这不是因为特定查询的执行成本很高,而是因为您生成的查询是不可共享的。由于您不能使用绑定(bind)变量(或者,如果您使用绑定(bind)变量,则需要不同数量的绑定(bind)变量)。这迫使 Oracle 每次都对查询进行相当昂贵的硬解析,并对您的共享池施加压力,这可能会迫使其他可共享的查询退出,从而导致系统中的硬解析更加困难。通常,您最好将要匹配的数据扔到临时表(甚至永久表)中,这样您的查询就可以共享并只解析一次。
根据 Branko 的评论,而 Oracle 在 IN
中限制为 1000 个文字。列表,仅当您使用“正常”语法时,即
WHERE projectID IN (1,2,3,...,N)
但是,如果您使用之前发布的元组语法,则可以拥有无限数量的元素。
因此,例如,如果我在
IN
中构建一个包含 2000 个项目的查询,我将收到错误消息。列表SQL> ed
Wrote file afiedt.buf
1 declare
2 l_sql_stmt varchar2(32000);
3 l_cnt integer;
4 begin
5 l_sql_stmt := 'select count(*) from emp where empno in (';
6 for i in 1..2000
7 loop
8 l_sql_stmt := l_sql_stmt || '(1),';
9 end loop;
10 l_sql_stmt := rtrim(l_sql_stmt,',') || ')';
11 -- p.l( l_sql_stmt );
12 execute immediate l_sql_stmt into l_cnt;
13* end;
SQL> /
declare
*
ERROR at line 1:
ORA-01795: maximum number of expressions in a list is 1000
ORA-06512: at line 12
但如果我使用元组语法
SQL> ed
Wrote file afiedt.buf
1 declare
2 l_sql_stmt varchar2(32000);
3 l_cnt integer;
4 begin
5 l_sql_stmt := 'select count(*) from emp where (empno,empno) in (';
6 for i in 1..2000
7 loop
8 l_sql_stmt := l_sql_stmt || '(1,1),';
9 end loop;
10 l_sql_stmt := rtrim(l_sql_stmt,',') || ')';
11 -- p.l( l_sql_stmt );
12 execute immediate l_sql_stmt into l_cnt;
13* end;
SQL> /
PL/SQL procedure successfully completed.
关于sql - Oracle 处理很长的 IN 运算符列表的效率如何,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9690974/