【问题标题】:Optimize select query on huge tables?优化大表上的选择查询?
【发布时间】:2016-04-28 05:26:38
【问题描述】:

我试图在给定索引列值的 Oracle 数据库中获取特定行。为了便于处理,我选择了索引列,然后是整个行数据。表上大约有 1.5 亿行,我选择了大约 100 - 20 万行。我正在使用 NVL() 替换 IN 子句中的空值,因为我在 JAVA 中动态创建这些语句。 IN 子句由 OR 拼凑成 1000 个索引块,以避免“列表中的最大表达式数为 1000”错误。

您可以想象,此查询需要很长时间才能运行。我可以做些什么来优化此查询但保持相同的功能? (忽略索引值,因为它们只是一个示例)

select INDEX_COLUMN_A, INDEX_COLUMN_B, INDEX_COLUMN_C, TABLE.* from TABLE     
Where (NVL(INDEX_COLUMN_A,''), NVL(INDEX_COLUMN_B,''), NVL(INDEX_COLUMN_C,'')) 
IN (('1','1','1'), ('2','2','2'), ... ('1000','1000','1000')) 
OR (NVL(INDEX_COLUMN_A,''), NVL(INDEX_COLUMN_B,''), NVL(INDEX_COLUMN_C,'')) 
IN (('1001','1001','1001'), ('1002','1002','1002'), ... ('2000','2000','2000')) 
OR...

【问题讨论】:

  • 在像NVL 这样的函数调用中包装索引列可以有效地防止Oracle 使用索引。您可以创建一个基于函数的索引,或者消除NVL 调用。
  • 如果您选择表的 1/1000 行,索引甚至可能没有用;无论如何,全表扫描可能会更快。
  • @MickMnemonic 啊,我没有意识到 NVL 会破坏索引。这是最不幸的。感谢您的信息。
  • 查询中使用的空字符串(我的意思是 '')被 Oracle 视为空值。所以像nvl(column_x,'') 这样的表达式没有任何意义,因为字面上等同于if column_x is null return null else return column_x
  • Oracle 不将空字符串视为 NULL 吗?但是为什么你认为你需要NVL,NULL 总是不同于'1'。此外,巨大的 IN 列表会降低性能(您是否检查过解析/优化它的时间),您最好将值加载到表中然后加入。

标签: java sql oracle optimization query-optimization


【解决方案1】:

如果在('1','1','1')('2000','2000','2000') 等测试值中不可能遇到null,您可以安全地避免nvl()

select 
  INDEX_COLUMN_A, INDEX_COLUMN_B, INDEX_COLUMN_C, 
  TABLE.* 
from 
  TABLE     
where 
  (INDEX_COLUMN_A, INDEX_COLUMN_B, INDEX_COLUMN_C) 
  in (
    ('1','1','1'), 
    ('2','2','2'), 
    ...,
    ('1000','1000','1000')
  ) 
  OR 
  (INDEX_COLUMN_A, INDEX_COLUMN_B, INDEX_COLUMN_C) 
  in (
    ('1001','1001','1001'), 
    ('1002','1002','1002'),
     ...,
    ('2000','2000','2000')
  ) 
  or
  ...

另外,a in (x1,x2) or a in (x3,x4) 这样的表达式表示((a=x1 or a=x2) or (a=x3 or a=x4))。在这种情况下可以省略括号而不会产生任何后果:(a=x1 or a=x2 or a=x3 or a=x4) 可以用in 表达式缩短为a in (x1, x2, x3, x4)。因此初始查询(如果要测试的值中没有空值)与以下相同:

select 
  INDEX_COLUMN_A, INDEX_COLUMN_B, INDEX_COLUMN_C, 
  TABLE.* 
from 
  TABLE     
where 
  (INDEX_COLUMN_A, INDEX_COLUMN_B, INDEX_COLUMN_C) 
  in (
    ('1','1','1'), 
    ('2','2','2'), 
    ...,
    ('1000','1000','1000')
    ...,
    ('1001','1001','1001'), 
    ('1002','1002','1002'),
     ...,
    ('2000','2000','2000')
  ) 

附言

a in (x,y,z) 只是由or :((a=x) or (a=y or (a=z))null 连接的一组相等关系的快捷方式,从不等于null,所以表达式null in (x,y,z) 永远不会返回true,而不管xyz 值。因此,如果您确实需要处理null 值,则必须将表达式更改为nvl(a,'some_never_encountered_value') in (nvl('1', 'some_never_encountered_value'), nvl('2','some_never_encountered_value'),...) 之类的东西。但在这种情况下,您不能在表上使用简单索引。可以构建功能索引来处理此类表达式,但这是一个非常不同的故事。

附言 如果列包含数字,则必须针对数字进行测试:您应该使用 (1,1,1) 而不是 (1,1,1)

【讨论】:

  • 感谢您的详细解答。我不确定我是否可以确保没有空值的可能性,因此我最初使用了 NVL。我需要保留 Or,因为没有它们,我得到“列表中的最大表达式数为 1000 错误”。最后,针对数字而不是字符串到数字测试数字是否会提高性能?还是仅仅是为了良好的编码礼仪?
【解决方案2】:

where 子句中使用or 也可以防止使用索引。您可以像这样替换查询

select *
  from table
 where <condition1> or <condition2>

select *
  from table
 where <condition1>
 union all
select *
  from table
 where <condition2>

反过来,第二个查询会产生重复的行(取决于数据)。为防止重复,您可以使用union 而不是union all,但使用union 也会引发性能问题。您需要进行大量实验,哪种方式最适合您的情况。

【讨论】:

  • 运行大约 100 - 200,000 SELECT/UNION 代替?
  • 使用 OR 真的会阻止索引的使用吗?我用它来防止“列表中的最大表达式数为 1000 错误”。
  • @JoshKni8 并非总是如此,但有时可以预防。大多数情况下,当您遇到a &gt; 100 or b &lt; 200 之类的条件时。在您的情况下(使用in (1, 2, 3, ...))将使用索引。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-08-04
  • 1970-01-01
  • 2011-04-11
  • 1970-01-01
  • 2023-04-09
  • 1970-01-01
相关资源
最近更新 更多