结果
如果您的索引被缓存,则基于函数的索引表现最佳。如果您的索引未缓存,则基于压缩函数的索引表现最佳。
以下是我的测试代码生成的相对时间。越低越好。您无法比较缓存和非缓存之间的数字,它们是完全不同的测试。
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 ...
将创建与创建表并以特定顺序执行一堆插入和删除不同的索引。