Sql Oracle处理一个很长的操作员列表的效率如何

Sql Oracle处理一个很长的操作员列表的效率如何,sql,oracle,oracle11g,Sql,Oracle,Oracle11g,我有以下查询(这是一个复杂得多的查询的简化版本): 在代码中,我将以编程方式构建(projectd,VERSIONID)键列表,这个列表可能有几千对长 我的问题是,如果ProjectId和VersionId都已编制索引,Oracle将如何优化此查询。该列表是否会转换为哈希表,类似于针对临时表的join?还是每次只查找一个键 我在测试数据库下尝试了此查询,得到: SELECT STATEMENT 68.0 68 2989732 19 8759 68

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

在代码中,我将以编程方式构建
(projectd,VERSIONID)
键列表,这个列表可能有几千对长

我的问题是,如果
ProjectId
VersionId
都已编制索引,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迭代器的含义。我猜这意味着Oracle正在遍历列表,并对列表中的每个项目进行表访问,如果有数千个键,这可能不会太有效。然而,如果我真的给了它几千个键,也许Oracle足够聪明,可以更好地优化它


注意:我不想将这些键加载到临时表中,因为坦率地说,我不喜欢临时表在Oracle下的工作方式,而且它们通常会遇到更多的挫折(无论如何,在我的非专家意见中)

优化器的决策应基于列表中的项目数和表中的行数。如果表有数百万行,而列表甚至有几千个项目,我通常认为它将使用索引进行几千个单行查找。如果表有几千行,列表有几千项,我希望优化器对表进行完整扫描。当然,在中间,所有有趣的事情都发生了,在哪里更难弄清楚优化器会选择什么样的计划。 但是,一般来说,从性能的角度来看,动态构建此类查询会有问题,这不是因为特定查询执行的成本有多高,而是因为您生成的查询不可共享。因为不能使用绑定变量(或者,如果使用绑定变量,则需要不同数量的绑定变量)。这迫使Oracle每次都要对查询进行一次相当昂贵的硬解析,并对您的共享池施加压力,这可能会强制执行其他可共享的查询,这将导致系统中更难解析。通常,将要匹配的数据扔到一个临时表(甚至是一个永久表)中会更好,这样您的查询就可以共享并解析一次

Branko评论说,虽然Oracle在列表中的
中限制为1000个文本,但这仅限于使用“正常”语法,即

WHERE projectID IN (1,2,3,...,N)
但是,如果使用前面发布的元组语法,则可以拥有无限数量的元素

因此,例如,如果我在
列表中的
中建立一个包含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.

不需要临时表的更好的解决方案可能是将数据放入PL/SQL表中,然后连接到该表。Tom Kyte有一个很好的例子:


希望这能有所帮助。

请注意,Oracle的限制是。Quote:“在表达式列表中最多可以指定1000个表达式。”。所以“几千双”是个问题。有趣!超过一千个是非常罕见的,但我可能不得不重新考虑我的设计。虽然这当然回避了一个问题:我是否可以使用
来分隔
表达式中的多个大
,一个la
,其中x IN(0,1999)或x IN(10011002,1003)
@BrankoDimitrijevic,Mike-实际上,如果使用最初发布的元组语法,则在
列表中的
中可以有1000多个元素。1000个元素的限制只适用于标量元素。@JustinCave不知道,谢谢!和往常一样,您是细微差别知识的真正来源。请注意,如果您加入PL/SQL表,优化器通常很难估计列表的基数,这很容易导致执行计划不佳。Tom就如何使用基数提示来解决这些问题进行了相关讨论,但随后您却在构建的每个查询中都添加了适当的提示。非常棒!我认为这就是我认为它会起作用的方式,尽管我认为Oracle可能足够聪明,可以在运行时将一个巨大的IN列表放入一个临时表中,并用它做一些聪明的事情。源表在生产中大约有250000行,in列表通常有几百对。每当有人将搜索数据导出到Excel时(可能一天一次或两次),都会运行此查询,因此可能不值得过度优化。。如果它能在几秒钟内运行,我会很高兴的。不管怎样,你的反馈再一次非常有用!
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.