在Date语句之间优化Oracle

时间:2012-11-21 13:41:58

标签: sql oracle indexing between

我有一个oracle SQL查询,它选择当天的条目,如下所示:

SELECT   [fields] 
FROM     MY_TABLE T 
WHERE    T.EVT_END BETWEEN TRUNC(SYSDATE) 
                       AND TRUNC(SYSDATE) + 86399/86400
  AND    T.TYPE = 123

EVT_END字段的类型为DATET.TYPENUMBER(15,0)

我确信随着表数据的大小(以及持续时间)的增加,日期约束将使结果集减少比类型约束大得多的因子。 (因为类型数量非常有限)

所以出现的基本问题是,选择在当前日期选择更快的最佳指标是什么。我特别想知道TRUNC(T.EVT_END)上的函数索引对T.EVT_END上的正常索引的优缺点是什么。使用功能索引时,查询看起来像这样:

SELECT   [fields] 
FROM     MY_TABLE T 
WHERE    TRUNC(T.EVT_END) = TRUNC(SYSDATE) 
  AND    T.TYPE = 123

因为其他查询使用上述日期约束而没有附加类型选择(或者可能包含其他一些字段),因此多列索引对我没有多大帮助。

谢谢,我很感激您的提示。

5 个答案:

答案 0 :(得分:4)

您的索引应为TYPE,EVT_END。

CREATE INDEX PIndex
ON MY_TABLE (TYPE, EVT_END)

优化器计划将首先浏览此索引以查找TYPE = 123部分。然后在TYPE = 123下,它将EVT_END时间戳排序,因此它可以在b树中搜索范围中的第一个日期,并按顺序浏览日期,直到数据超出范围。

答案 1 :(得分:3)

根据上面的查询,功能索引不会提供任何值。对于要使用的功能索引,查询中的谓词需要按如下方式编写:

SELECT [fields] 
FROM MY_TABLE T 
WHERE TRUNC(T.EVT_END) BETWEEN TRUNC(SYSDATE) AND TRUNC(SYSDATE) + 86399/86400
  AND T.TYPE = 123

正在忽略EVT_END列上的功能索引。最好在EVT_END日期有一个正常的索引。对于要使用的功能索引,条件的左侧必须与功能索引的声明匹配。我可能会将查询写成:

SELECT [fields] 
FROM MY_TABLE T 
WHERE T.EVT_END BETWEEN TRUNC(SYSDATE) AND TRUNC(SYSDATE+1)
  AND T.TYPE = 123

我会创建以下索引:

CREATE INDEX bla on MY_TABLE( EVT_END )

这假设您正在尝试查找一天内结束的事件。

答案 2 :(得分:1)

@ S1lence:
我相信在您提出这个问题背后会有相当长的一段时间的思考。而且,我花了很多时间在这里发布我的答案,因为我不喜欢发布任何答案的猜测 我想在针对FBI的日期专栏中分享我对此常规索引选择的网络搜索经验 根据我对下面的链接的理解,如果您要使用TRUNC函数肯定,那么您可以删除正常索引的选项,因为这个咨询网站空间说:
即使列可能有索引,trunc内置函数也会使索引无效,导致使用不必要的I / O进行次优执行。
我想这一切都清楚了。如果您肯定会使用TRUNC,那么您应该选择FBI。如果我的回复有意义,请告诉我。

Oracle SQL Tuning with function-based indexes

干杯,
Lakshmanan C.

答案 3 :(得分:1)

关于是否使用基于函数的索引的决定应该由您计划编写查询的方式决定。如果针对日期列的所有查询都采用TRUNC(EVT_END)形式,那么您应该使用FBI。但是,通常最好只在EVT_END上创建一个索引,原因如下:

  • 它会更可重复使用。如果您有查询当天的特定时间,则无法使用TRUNC。
  • 只使用日期,索引中会有更多不同的键。如果您在一天中插入了1,000个不同的时间,EVT_END将有1,000个不同的键,而TRUNC(EVT_END)将只有1个(这假设您存储时间组件而不仅仅是午夜所有日期 - 在第二种情况下,一天都有1个不同的密钥)。这很重要,因为索引具有更明确的值,索引的选择性越高,优化器使用它的可能性就越大(参见this
  • 群集因素可能会有所不同,但在使用trunc的情况下,它更有可能上升,而不是像其他一些评论中所说的那样下降。这是因为聚类因子表示索引中值的顺序与数据的物理存储的匹配程度。如果所有数据都按日期顺序插入,那么普通索引将具有与物理数据相同的顺序。但是,TRUNC一天中的所有时间都会映射到相同的值,因此索引中的行顺序可能与物理数据完全不同。同样,这意味着不太可能使用trunc索引。这完全取决于数据库的插入/删除模式。
  • 开发人员更有可能针对未应用于列的位置编写查询(根据我的经验)。这是否适用于您将取决于您的开发人员以及您在部署SQL时的质量控制。

就个人而言,我会将马林的回答TYPE, EVT_END作为第一关。您需要在您的环境中对此进行测试,然后使用TYPE和EVT_END列查看这对此查询及其他所有查询的影响。

答案 4 :(得分:1)

<强>结果

如果您的索引已缓存,则基于函数的索引效果最佳。如果未缓存索引,则基于函数的压缩索引效果最佳。

以下是我的测试代码生成的相对时间。越低越好。你无法比较缓存和非缓存之间的数字,它们是完全不同的测试。

                 In cache      Not in cache
Regular          120           139
FBI              100           138
Compressed FBI   126           100

我不确定为什么FBI比常规指数表现更好。 (虽然它可能与您所说的关于等式谓词与范围的内容有关。您可以看到常规索引在其解释计划中有一个额外的“过滤器”步骤。)压缩的FBI有一些额外的开销来解压缩块。当一切都已经在内存中时,这个少量的额外CPU时间是相关的,并且CPU等待是最重要的。但是当没有任何缓存,IO更重要时,压缩FBI的缩小空间有很大帮助。

<强>假设

这个问题似乎有很多混乱。我读它的方式,你只关心这个特定的查询,并且你想知道基于函数的索引或常规索引是否会更快。

我假设您不关心可能从此索引中受益的其他查询,维护索引所花费的额外时间,开发人员是否记得使用它,或者优化程序是否选择索引。 (如果优化器没有选择索引,我认为不太可能,你可以添加一个提示。)如果有任何这些假设是错误的,请告诉我。

<强>代码

--Create tables. 1 = regular, 2 = FBI, 3 = Compressed FBI
create table my_table1(evt_end date, type number) nologging;
create table my_table2(evt_end date, type number) nologging;
create table my_table3(evt_end date, type number) nologging;

--Create 1K days, each with 100K values
begin
    for i in 1 .. 1000 loop
        insert /*+ append */ into my_table1
        select sysdate + i - 500 + (level * interval '1' second), 1
        from dual connect by level <= 100000;

        commit;
    end loop;
end;
/
insert /*+ append */ into my_table2 select * from my_table1;
insert /*+ append */ into my_table3 select * from my_table1;

--Create indexes
create index my_table1_idx on my_table1(evt_end);
create index my_table2_idx on my_table2(trunc(evt_end));
create index my_table3_idx on my_table3(trunc(evt_end)) compress;

--Gather statistics
begin
    dbms_stats.gather_table_stats(user, 'MY_TABLE1');
    dbms_stats.gather_table_stats(user, 'MY_TABLE2');
    dbms_stats.gather_table_stats(user, 'MY_TABLE3');
end;
/

--Get the segment size.
--This shows the main advantage of a compressed FBI, the lower space.
select segment_name, bytes/1024/1024/1024 GB
from dba_segments
where segment_name like 'MY_TABLE__IDX'
order by segment_name;

SEGMENT_NAME     GB
MY_TABLE1_IDX    2.0595703125
MY_TABLE2_IDX    2.0478515625
MY_TABLE3_IDX    1.1923828125


--Test block.
--Uncomment different lines to generate 6 different test cases.
--Regular, Function-based, and Function-based compressed.  Both cached and not-cached.
declare
    v_count number;
    v_start_time number;
    v_total_time number := 0;
begin
    --Uncomment two lines to test the server when it's "cold", and nothing is cached.
    for i in 1 .. 10 loop
        execute immediate 'alter system flush buffer_cache';
    --Uncomment one line to test the server when it's "hot", and everything is cached.
    --for i in 1 .. 1000 loop

        v_start_time := dbms_utility.get_time;

        SELECT COUNT(*)
        INTO   V_COUNT
        --#1: Regular
        FROM   MY_TABLE1 T 
        WHERE  T.EVT_END BETWEEN TRUNC(SYSDATE) AND TRUNC(SYSDATE) + 86399/86400;
        --#2: Function-based
        --FROM   MY_TABLE2 T 
        --WHERE  TRUNC(T.EVT_END) = TRUNC(SYSDATE);
        --#3: Compressed function-based
        --FROM   MY_TABLE3 T 
        --WHERE  TRUNC(T.EVT_END) = TRUNC(SYSDATE);

        v_total_time := v_total_time + (dbms_utility.get_time - v_start_time);
    end loop;

    dbms_output.put_line('Seconds: '||v_total_time/100);
end;
/

测试方法

我运行每个块至少5次,在运行类型之间交替(如果某些东西仅在我的机器上运行的部分时间),抛出高和低运行时间,并对它们求平均值。上面的代码不包含所有逻辑,因为它占据了这个答案的90%。

需要考虑的其他事项

还有许多其他事情需要考虑。我的代码假设数据以非常友好的索引顺序插入。如果不是这样的话,情况会完全不同,因为压缩可能根本无济于事。

这个问题的最佳解决方案可能是完全避免分区。对于读取相同数量的数据,全表扫描比索引读取快得多,因为它使用多块IO。但是分区存在一些缺点,比如大量资金 需要购买选件和额外的维护任务。例如,提前创建分区,或使用区间分区(有一些其他奇怪的问题),收集统计信息,延迟段创建等。

最终,您需要自己测试一下。但请记住,测试即使这么简单的选择也很困难。您需要真实的数据,真实的测试和现实的环境。现实数据比听起来要困难得多。使用索引,您不能简单地复制数据并立即构建索引。 create table my_table1 as select * fromcreate index ...将创建一个不同的索引,而不是创建表并按特定顺序执行一堆插入和删除。