【问题标题】:Index not used due to type conversion?由于类型转换而未使用索引?
【发布时间】:2009-07-22 00:49:48
【问题描述】:

由于对特定表进行全表扫描,我有一个进程性能不佳。我已经计算了统计数据,重建了现有索引并尝试为该表添加新索引,但这并没有解决问题。

隐式类型转换可以停止正在使用的索引吗?其他原因呢?全表扫描的成本比索引查找的成本高出大约 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

【问题讨论】:

  • 能否提供问题查询?
  • 能否提供执行查询的代码sn-p?

标签: database performance oracle indexing


【解决方案1】:

隐式转换可以防止优化器使用索引。考虑:

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)。如果我们想使用索引,我们必须是explicit

select * from a where id = '100';

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

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

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

来自 Oracle 文档:

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

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

【讨论】:

  • 它总是阻止索引被使用吗?或者有时?
  • 非常好:select rownum from dual connect by rownum
【解决方案2】:

让你条件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

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

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

【讨论】:

  • 我想也可以在 Trunc(date) 上创建一个基于函数的索引作为替代方案。
  • @David:是的,但这会使您的表成为一个索引更重,而没有任何额外的好处。 TRUNC 是一个连续函数,即TRUNC 上的任何连续范围都可以表示为原始日期上的连续范围。例如,UPPER 不是。 UPPER 上的连续范围(如UPPER(col1) BETWEEN 'AAA' AND 'BBB')无法通过重写原始查询来表示,这就是为什么UPPER 上的索引很有用,而TRUNC 上的索引没有用。
【解决方案3】:

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

您的查询是:

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 程序员一开始就做好。

【讨论】:

  • 这解决了我的问题。在没有访问源代码的情况下,我能够创建一个基于函数的索引来避免隐式类型转换。
【解决方案4】:

如果您使用围绕参数的显式转换(例如,适当的 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;

我不是索引提示的忠实粉丝——它们是一种非常脆弱的编程方法。如果名称在未来的索引中更改,您永远不会知道它,直到您的查询开始表现不佳,另外,如果服务器升级或数据分布更改导致优化器能够选择,您可能会自责一个更好的计划。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-03
    • 1970-01-01
    相关资源
    最近更新 更多