5

我有一个 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字段是类型DATE并且T.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

因为其他查询使用提到的日期约束而没有额外的类型选择(或者可能使用其他一些字段),所以多列索引对我没有多大帮助。

谢谢,我会很感激你的提示。

4

5 回答 5

4

您的索引应该是 TYPE,EVT_END。

CREATE INDEX PIndex
ON MY_TABLE (TYPE, EVT_END)

优化器计划将首先通过该索引找到 TYPE=123 部分。然后在 TYPE=123 下,它会对 EVT_END 时间戳进行排序,因此它可以在 b-tree 中搜索范围内的第一个日期,并按顺序遍历这些日期,直到数据超出范围。

于 2012-11-21T13:54:05.007 回答
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 )

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

于 2012-11-27T17:43:53.150 回答
1

@S1lence:
我相信您提出的这个问题背后会有相当长的思考时间。而且,我花了很多时间在这里发布我的答案,因为我不喜欢发布任何猜测答案。
我想分享我在针对 FBI 的日期列上选择正常索引的网络搜索经验。
根据我对下面链接的理解,如果您确定要使用 TRUNC 功能,那么您可以取消普通索引的选项,因为这个咨询网站空间说:
即使列可能有索引,trunc内置函数将使索引无效,导致不必要的 I/O 的次优执行。
我想这一切都清楚了。如果你要使用,你必须和 FBI 一起去TRUNC当然。请让我知道我的回复是否有意义。

使用基于函数的索引进行 Oracle SQL 调优

干杯,
Lakshmanan C.

于 2012-11-28T11:00:41.740 回答
1

关于是否使用基于函数的索引的决定应该由您计划如何编写查询来决定。如果您对日期列的所有查询都将在表单TRUNC(EVT_END)中,那么您应该使用 FBI。EVT_END但是,一般而言,仅出于以下原因创建索引会更好:

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

就个人而言,我会首先接受马林的回答TYPE, EVT_END。但是,您需要在您的环境中对此进行测试,并查看这如何影响此查询以及使用 TYPE 和 EVT_END 列的所有其他查询。

于 2012-12-02T12:19:43.003 回答
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 * from并且create index ...将创建与创建表并以特定顺序执行一堆插入和删除不同的索引。

于 2012-12-02T23:18:05.520 回答