由于特定表上的全表扫描,我的进程表现不佳。我已经计算了统计数据,重建了现有的索引,并尝试为此表添加新索引,但这还没有解决问题。
隐式类型转换是否可以停止使用索引?其他原因呢?全表扫描的成本比索引查找大1000左右。
编辑:
SQL语句:
select unique_key
from src_table
where natural_key1 = :1
and natural_key2 = :2
and natural_key3 = :3;
Java代码(不易修改):
ps.setLong(1, oid);
这与列数据类型:varchar2
冲突答案 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_key1
是varchar2
而: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;
我不是索引提示的忠实粉丝 - 它们是一种非常脆弱的编程方法。如果名称在路上发生了变化,那么在您的查询开始表现不佳之前,您永远不会知道它,如果服务器升级或数据分发更改导致优化程序能够选择,您可能会自己开始行动一个更好的计划。