由于类型转换而未使用索引?

时间:2009-07-21 09:37:09

标签: database performance oracle indexing

由于特定表上的全表扫描,我的进程表现不佳。我已经计算了统计数据,重建了现有的索引,并尝试为此表添加新索引,但这还没有解决问题。

隐式类型转换是否可以停止使用索引?其他原因呢?全表扫描的成本比索引查找大1000左右。

编辑:

SQL语句:

select unique_key 
from src_table 
where natural_key1 = :1 
and natural_key2 = :2 
and natural_key3 = :3;
  • natural_key1的基数很高,但有类型转换。
  • 自然键的其他部分是低基数,并且未启用位图索引。
  • 表格大小约为1,000,000条记录。

Java代码(不易修改):

ps.setLong(1, oid);

这与列数据类型:varchar2

冲突

4 个答案:

答案 0 :(得分:11)

隐式转换可以阻止优化程序使用索引。考虑:

SQL> CREATE TABLE a (ID VARCHAR2(10) PRIMARY KEY);

Table created

SQL> insert into a select rownum from dual connect by rownum <= 1e6;

1000000 rows inserted

这是一个简单的表,但数据类型不是'正确的',如果您像这样查询它将完全扫描:

SQL> select * from a where id = 100;

ID
----------
100

此查询实际上等同于:

select * from a where to_number(id) = 100;

由于我们已将id编入索引而非to_number(id),因此无法使用该索引。如果我们想要使用索引,我们必须显式

select * from a where id = '100';

回复pakr的评论: 关于隐式转换有很多规则。一个好的开始是documentation。除其他外,我们了解到:

  

在SELECT FROM操作期间,Oracle将列中的数据转换为目标变量的类型。

这意味着当在"WHERE column=variable"子句期间发生隐式转换时,Oracle将转换列的数据类型而不转换变量,从而阻止使用索引。这就是为什么你应该总是使用正确的数据类型或显式转换变量。

来自Oracle doc:

  

Oracle建议您指定显式转换,而不是依赖隐式或自动转换,原因如下:

     
      
  • 使用显式数据类型转换函数时,SQL语句更容易理解。
  •   
  • 隐式数据类型转换可能会对性能产生负面影响,尤其是当列值的数据类型转换为常量的数据类型而不是相反时。
  •   
  • 隐式转换取决于它发生的上下文,并且在每种情况下可能无法以相同的方式工作。例如,从datetime值到VARCHAR2值的隐式转换可能会返回意外的年份,具体取决于NLS_DATE_FORMAT参数的值。
  •   
  • 隐式转换的算法可能会在软件版本和Oracle产品之间发生变化。显式转换的行为更具可预测性。
  •   

答案 1 :(得分:8)

让你条件 sargable ,即将字段本身与常量条件进行比较。

这很糟糕:

SELECT  *
FROM    mytable
WHERE   TRUNC(date) = TO_DATE('2009.07.21')

,因为它无法使用索引。 Oracle无法反转TRUNC()函数来获取范围界限。

这很好:

SELECT  *
FROM    mytable
WHERE   date >= TO_DATE('2009.07.21')
        AND date < TO_DATE('2009.07.22')

要摆脱隐式转换,请使用显式转换:

这很糟糕:

SELECT  *
FROM    mytable
WHERE   guid = '794AB5396AE5473DA75A9BF8C4AA1F74'

-- This uses implicit conversion. In fact this is RAWTOHEX(guid) = '794AB5396AE5473DA75A9BF8C4AA1F74'

这很好:

SELECT  *
FROM    mytable
WHERE   guid = HEXTORAW('794AB5396AE5473DA75A9BF8C4AA1F74')

<强>更新

此查询:

SELECT  unique_key
FROM    src_table
WHERE   natural_key1 = :1
        AND natural_key2 = :2
        AND natural_key3 = :3

在很大程度上取决于您的字段类型。

将变量显式地转换为字段类型,就好像来自字符串一样。

答案 2 :(得分:1)

您可以使用基于函数的索引。

您的查询是:

select
    unique_key 
from
    src_table
where
    natural_key1 = :1

在您的情况下,未使用索引,因为natural_key1varchar2:1是一个数字。 Oracle正在将您的查询转换为:

select
    unique_key 
from
    src_table
where
    to_number(natural_key1) = :1

所以......加上to_number(natural_key1)的索引:

create index ix_src_table_fnk1 on src_table(to_number(natural_key1));

您的查询现在将使用ix_src_table_fnk1索引。

当然,最好让Java程序员首先正确地完成它。

答案 3 :(得分:1)

如果您使用围绕参数的显式转换(例如,to_char(:1)或to_number(:1))运行它,您的查询会发生什么?如果这样做会使您的查询运行得很快,那么您就得到了答案。

但是,如果您的查询在显式转换时仍然运行缓慢,则可能存在其他问题。您没有提到您正在运行的Oracle版本,如果您的高基数列(natural_key1)的值具有非常偏差的分布,您可能正在使用首次运行查询时生成的查询计划,该计划使用了不利的价值:1。

例如,如果您的100万行表有400,000行,其中natural_key1 = 1234,其余600,000是唯一的(或几乎如此),如果您的查询限制在natural_key1 = 1234,优化程序将不会选择索引。您正在使用绑定变量,如果这是您第一次运行查询,优化程序将为所有后续运行选择该计划。

测试此理论的一种方法是在运行测试语句之前运行此命令:

alter system flush shared_pool;

这将从优化器的大脑中删除所有查询计划,因此下一个语句运行将进行新的优化。或者,您可以将语句作为带有文字的直接SQL运行,而不是绑定变量。如果它在任何一种情况下运行良好,你就会知道你的问题是由于计划损坏。

如果是这种情况,你不想在生产中使用那个alter system命令 - 你可能会破坏系统性能的其余部分,如果你经常运行它,但你可以通过使用动态sql绕过它而不是绑定变量,或者如果可以提前确定:1是非选择性的,则对非选择性情况使用稍微不同的查询(例如重新排序WHERE子句中的条件,这将导致优化器使用不同的计划。)

最后,您可以尝试在查询中添加索引提示,例如:

  SELECT /*+ INDEX(src_table,<name of index for natural_key1>) */
         unique_key
    FROM src_table
   WHERE natural_key1 = :1
     AND natural_key2 = :2
     AND natural_key3 = :3;

我不是索引提示的忠实粉丝 - 它们是一种非常脆弱的编程方法。如果名称在路上发生了变化,那么在您的查询开始表现不佳之前,您永远不会知道它,如果服务器升级或数据分发更改导致优化程序能够选择,您可能会自己开始行动一个更好的计划。