我有以下Oracle表:
create table my_table(
start int,
end int
);
insert into my_table values(1, 3);
insert into my_table values(5, 7);
insert into my_table values(11, 200);
insert into my_table values(311, 5000);
insert into my_table values(60004, 60024);
insert into my_table values(123213, 12312312);
此表有1M行并存储数字范围('start','end'),所有数字都是唯一的,它没有重复的范围,任何数字只能在这个表的一个范围内,我有以下查询它传递一个变量my_number来标识范围的“开始”。
execute immediate
'select start from my_table where :1 between start and end' using my_number
我在两个字段上构建了组合索引。问题是当my_number很小时,查询的性能很好,但是当my_number增加时,查询时间会不断增加。如果my_number更大,则需要花费相当长的时间才能完成。有人有办法改进这个查询吗?方式可以包括重新设计my_table。谢谢。
答案 0 :(得分:2)
如果您将架构更改为:
create table my_table(
start int,
range_size int
);
insert into my_table values(1, 2);
insert into my_table values(5, 2);
insert into my_table values(11, 189);
insert into my_table values(311, 4689);
insert into my_table values(60004, 20);
insert into my_table values(123213, 12300001);
然后,您只能在start
列上编制索引。
execute immediate
'select start from (select start, range_size from my_table where start < :1 order by start asc limit 1) tmp where :1 < start+range_size' using my_number
这可能会有一些性能提升。
答案 1 :(得分:0)
你做完了吗?
create table my_table(
start int,
end int
constraint PK_comp primary key (start, end)
) ;
答案 2 :(得分:0)
我认为你应该制作两个索引,一个在开始列,一个在结束列。 然后选择不使用between选项,但选择大于start然后小然后结束。 然后,您将为每个where子句使用索引。
我希望这有助于提升绩效。
答案 3 :(得分:0)
这是一个试图欺骗甲骨文表现得像竞争对手的情况,而且无法访问甲骨文,我只是在猜测。也许自我加入可以做到这一点?每列上的索引,
SELECT t1.start
FROM my_table t1 JOIN my_table t2
ON t1.start=t2.start AND t2."end"=t1."end"
AND t1.start <= :1
AND t2.end >= :1
这看起来很傻,但直截了当的解决方案是Joe Frambach。它愚弄Postgres,我确实只做索引搜索。
BTW,Postgres对end
作为列名非常不满。我希望你的真实桌子不会在那里使用保留字。
答案 4 :(得分:0)
为每列创建索引并使用此查询:
select start_num
from my_table
where
start_num =
(
--Last start <= number
select start_num
from
(
select start_num
from my_table
where :1 >= start_num
order by start_num desc
)
where rownum = 1
) and
end_num =
(
--First end >= number
select end_num
from
(
select end_num
from my_table
where :1 <= end_num
order by end_num
)
where rownum = 1
);
呸。写这个可能是更好的方法。或者您可能希望将其包装在函数中。
问题
测试数据(带有非保留字列名称):
drop table my_table;
create table my_table(
start_num int,
end_num int
);
insert into my_table select level*2,level*2+1 from dual connect by level <= 1000000;
commit;
create index my_table_index on my_table(start_num, end_num);
begin
dbms_stats.gather_table_stats(user, 'MY_TABLE', no_invalidate => false);
end;
/
低数字几乎是瞬间的 - 0.015秒
select start_num from my_table where 2 between start_num and end_num;
较大的数字较慢 - 0.125秒
select start_num from my_table where 1000000 between start_num and end_num;
范围扫描和全表扫描之间只有一个点。
explain plan for select start_num from my_table where 402741 between start_num and end_num;
select * from table(dbms_xplan.display);
Plan hash value: 3804444429
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 160K| 1570K| 622 (2)| 00:00:08 |
|* 1 | TABLE ACCESS FULL| MY_TABLE | 160K| 1570K| 622 (2)| 00:00:08 |
------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("START_NUM"<=402742 AND "END_NUM">=402742)
explain plan for select start_num from my_table where 402742 between start_num and end_num;
select * from table(dbms_xplan.display);
Plan hash value: 3804444429
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 160K| 1570K| 622 (2)| 00:00:08 |
|* 1 | TABLE ACCESS FULL| MY_TABLE | 160K| 1570K| 622 (2)| 00:00:08 |
------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("START_NUM"<=402742 AND "END_NUM">=402742)
但问题不在于Oracle不使用索引。以天真的方式使用索引并没有帮助。事实上,这甚至更慢,为0.172秒:
select /*+ index(my_table my_table_index) */ start_num
from my_table
where 1000000 between start_num and end_num;
<强>解决方案强>
创建新索引:
drop index my_table_index;
create index my_table_index1 on my_table(start_num);
create index my_table_index2 on my_table(end_num);
begin
dbms_stats.gather_table_stats(user, 'MY_TABLE', no_invalidate => false);
end;
/
对于任何数字,结果都是即时的:
select start_num
from my_table
where
start_num =
(
--Last start <= number
select start_num
from
(
select start_num
from my_table
where 1000000 >= start_num
order by start_num desc
)
where rownum = 1
) and
end_num =
(
--First end >= number
select end_num
from
(
select end_num
from my_table
where 1000000 <= end_num
order by end_num
)
where rownum = 1
);
该计划看起来很棒 - 这可能是您可以获得的最佳表现。
Plan hash value: 522166032
--------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 10 | 10 (0)| 00:00:01 |
|* 1 | TABLE ACCESS BY INDEX ROWID | MY_TABLE | 1 | 10 | 4 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | MY_TABLE_INDEX2 | 1 | | 3 (0)| 00:00:01 |
|* 3 | COUNT STOPKEY | | | | | |
| 4 | VIEW | | 3 | 39 | 3 (0)| 00:00:01 |
|* 5 | INDEX RANGE SCAN | MY_TABLE_INDEX2 | 3 | 18 | 3 (0)| 00:00:01 |
|* 6 | COUNT STOPKEY | | | | | |
| 7 | VIEW | | 2 | 26 | 3 (0)| 00:00:01 |
|* 8 | INDEX RANGE SCAN DESCENDING| MY_TABLE_INDEX1 | 500K| 2929K| 3 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("START_NUM"= (SELECT "START_NUM" FROM (SELECT "START_NUM" "START_NUM" FROM
"MY_TABLE" "MY_TABLE" WHERE "START_NUM"<=1000000 ORDER BY "START_NUM" DESC)
"from$_subquery$_002" WHERE ROWNUM=1))
2 - access("END_NUM"= (SELECT "END_NUM" FROM (SELECT "END_NUM" "END_NUM" FROM
"MY_TABLE" "MY_TABLE" WHERE "END_NUM">=1000000 ORDER BY "END_NUM") "from$_subquery$_004"
WHERE ROWNUM=1))
3 - filter(ROWNUM=1)
5 - access("END_NUM">=1000000)
6 - filter(ROWNUM=1)
8 - access("START_NUM"<=1000000)