Oracle如何有效地处理非常长的IN运算符列表

时间:2012-03-13 19:47:12

标签: sql oracle oracle11g

我有以下查询(这是一个更复杂的查询的简化版本):

SELECT * FROM TPM_TASK
WHERE (PROJECTID, VERSIONID) IN ((3,1), (24,1), (4,1))

在代码中,我将以编程方式构建(PROJECTID,VERSIONID)密钥列表,此列表可能长达数千对。

我的问题是,如果ProjectIdVersionId被编入索引,Oracle将如何优化此查询。将列表转换为哈希表,类似于临时表的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正在遍历列表并对列表中的每个项目进行表访问,这对于数千个密钥来说可能效率不高。但是,如果我实际 给它几千个密钥,那么Oracle可能足够聪明,可以更好地优化它。

注意:我不想将这些密钥加载到临时表中,因为坦率地说我不喜欢临时表在Oracle下运行的方式,而且它们通常比他们更沮丧非常值得(无论如何我的非专家意见。)

2 个答案:

答案 0 :(得分:8)

优化器应根据列表中的项目数和表中的行数做出决策。如果表有数百万行,并且列表甚至有几千个项目,我通常会期望它会使用索引进行几千次单行查找。如果表有几千行并且列表有几千个项目,我希望优化器能够对表进行全面扫描。当然,在中间,所有有趣的东西都会发生,并且更难以确定优化程序将选择的计划。

但是,一般情况下,从性能角度来看,动态构建此类查询会产生问题,而不是因为特定查询执行的成本有多高,而是因为您生成的查询不可共享。由于您不能使用绑定变量(或者,如果您使用绑定变量,则需要不同数量的绑定变量)。这迫使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.

答案 1 :(得分:1)

更好的解决方案,不需要临时表,可能是将数据放入PL / SQL表,然后加入它。 Tom Kyte在这里有一个很好的例子: PL/SQL Table join example

希望有所帮助。