Oracle不使用SYS_CONTEXT在动态查询中选择正确的索引

时间:2018-04-23 08:23:30

标签: sql oracle

我有一个由三个主要字段组成的表:" begin_time"," end_time"和" login_name"。为了提高查询速度,我在这三个字段上创建了一个索引。现在我有一个查询,它使用SYS_CONTENT函数,如下所示:

select * from tbl1 R
where
begin_time <= TO_DATE(SYS_CONTEXT('CTX_A', 'RES_END_TIME'), 'yyyy-mm-dd hh24:mi:ss')
AND end_time >= TO_DATE(GREATEST(SYS_CONTEXT('CTX_A', 'rptBeginTime'), SYS_CONTEXT('CTX_A', 'RES_BEGIN_TIME')), 'yyyy-mm-dd hh24:mi:ss')
and (
    SYS_CONTEXT('CTX_A', 'RES_LOGIN_NAME') = '*'
    or (
        login_name in (
            select regexp_substr(SYS_CONTEXT('CTX_A', 'RES_LOGIN_NAME'), '[^, ]+',1, rownum) str
            from dual connect by level <= regexp_count SYS_CONTEXT('CTX_A', 'RES_LOGIN_NAME'), '[^, ]+')
            )
        )
    )
)

查询的工作方式如下:输入&#34; RES_LOGIN_NAME&#34;是一个逗号分隔列表,它将在日期期间搜索正确登录名的记录。如果输入为&#39; *&#39;,则会搜索日期内的所有记录。

从功能上讲,这个查询对我来说非常合适。如果我将所有SYS_CONTEXT(...)替换为某些具体值,Oracle知道如何选择正确的索引(使用正确的索引,包含所有三个字段)。但是,在SYS_CONTEXT的上述情况下,Oracle仍然会从&#34;解释计划中选择正确的索引,我发现它只选择begin_time和end_time作为索引的谓词,排除&#34; login_name&#34;。

我想知道为什么Oracle在将谓词安装到索引时从谓词中排除了login_name,以及我可以强制Oracle在索引中包含login_name的任何方法?

其他信息 如果我删除&#34; SYS_CONTEXT(&#39; CTX_A&#39;,&#39; RES_LOGIN_NAME&#39;)=&#39; *&#39;或&#34;,然后此查询执行得非常快。

1 个答案:

答案 0 :(得分:0)

原因可能与begin_time和end_time索引上的直方图以及Oracle无法预先确定函数调用结果有关。

首先要理解的是,使用索引不一定是最佳选择,特别是如果查询已经使用了一个索引。使用索引会产生开销 - Oracle必须转到索引,获取索引信息,然后使用它来查找数据块。如果要获取大量行,有时只需使用全表扫描获取数据就更容易了。

其次,如果你已经在使用另一个索引,那就更难了,因为Oracle甚至不能使用第二个索引来直接获取块;它必须对每个索引的行进行散列连接,这可能很昂贵。同样,从第二个条件中过滤行可能更好。

Oracle决定使用什么策略的方法是使用索引中的直方图来计算将返回的行数并计算出最有效的策略。

问题是,如果你使用像SYS_CONTEXT这样的函数,甲骨文真的不知道该函数会返回什么值,那么就不能准确估计条件选择的行数(不像指定文字值时。)

我的第一个建议是询问您是否确实遇到了真正的性能问题(实际上您是否需要选择登录索引)。如果性能良好,则假设Oracle知道它在做什么。

如果性能不佳,您可以尝试在select语句中添加提示以强制使用索引。例如:

select /*+ index(index_name) */ * from table_name;

请参阅此文章:Oracle index hint syntax

另一个选项是预先评估代码中的SYS_CONTEXT函数调用(可能只需要执行一次),然后将这些函数的输出作为文字传递给查询。根据您使用的环境和编程语言,这可能是也可能不实用,但这种方法在使优化器运行良好方面非常有效。

如果你不能这样做,你可以尝试欺骗Oracle认为login_name始终在查询中。例如:

and
    login_name in (
        select login_name from dual where SYS_CONTEXT('CTX_A', 'RES_LOGIN_NAME') = '*'
        union
        select regexp_substr(SYS_CONTEXT('CTX_A', 'RES_LOGIN_NAME'), '[^, ]+',1, rownum) str
        from dual connect by level <= regexp_count SYS_CONTEXT('CTX_A', 'RES_LOGIN_NAME'), '[^, ]+'
        where SYS_CONTEXT('CTX_A', 'RES_LOGIN_NAME') <> '*')
        )
    )