受this question的启发我决定测试rank()
函数,试图查看子查询的效率是否低于排名。所以我创建了一个表:
create table teste_rank ( codigo number(7), data_mov date, valor number(14,2) );
alter table teste_rank add constraint tst_rnk_pk primary key ( codigo, data_mov );
并插入一些记录......
declare
vdata date;
begin
dbms_random.initialize(120401);
vdata := to_date('04011997','DDMMYYYY');
for reg in 1 .. 465 loop
vdata := to_date('04011997','DDMMYYYY');
while vdata <= trunc(sysdate) loop
insert into teste_rank
(codigo, data_mov, valor)
values
(reg, vdata, dbms_random.value(1,150000));
vdata := vdata + 2;
end loop;
commit;
end loop;
end;
/
然后测试了两个查询:
select *
from teste_rank r
where r.data_mov = ( select max(data_mov)
from teste_rank
where data_mov <= trunc(sysdate)
and codigo = 1 )
and r.codigo = 1;
select *
from ( select rank() over ( partition by codigo order by data_mov desc ) rn, t.*
from teste_rank t
where codigo = 1
and data_mov <= trunc(sysdate) ) r
where r.rn = 1;
如您所见,子查询的成本低于rank()。这是正确的吗?我错过了什么吗?
PS:在表格中使用完整查询进行测试,但仍以低成本进行子查询。
修改
我生成了两个查询的tkprof(跟踪一个,关闭数据库,启动并追踪第二个)。
对于子查询
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.02 3 5 0 0
Execute 1 0.00 0.00 0 3 0 0
Fetch 2 0.00 0.00 1 4 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.00 0.02 4 12 0 1
rank()
call count cpu elapsed disk query current rows
------- ------ -------- ---------- ---------- ---------- ---------- ----------
Parse 1 0.00 0.02 3 3 0 0
Execute 1 0.00 0.00 0 0 0 0
Fetch 2 0.00 0.00 9 19 0 1
------- ------ -------- ---------- ---------- ---------- ---------- ----------
total 4 0.01 0.03 12 22 0 1
我能否断定子查询的效率总是低于排名?何时表示排名而不是子查询?
答案 0 :(得分:2)
成本是基于成本的优化程序对执行查询所需内容的估计。
CBO可能会弄错,特别是如果统计数据已经过时。
那么,该怎么办?尝试使用'set autotrace on'执行每个查询。每个查询执行多少缓冲区获取和物理读取?换句话说,每个查询执行了多少实际工作?
希望有所帮助。
答案 1 :(得分:2)
我不确定你的问题是什么。是的,根据这两个执行计划,在这种情况下,子查询方法具有较低的预期成本。似乎并不太令人惊讶,因为它可以使用索引非常快速地找到您感兴趣的确切行。特别是在这种情况下,子查询只需要快速扫描PK索引。如果子查询涉及不属于索引的列,则情况可能会有所不同。
使用rank()
的查询必须获取所有匹配的行并对其进行排名。我不相信优化器有任何短路逻辑来识别这是一个前n个查询,因此避免完全排序,即使你真正关心的是排名第一的行。
您也可以尝试使用此格式,优化程序应将其识别为前n个查询。在您的情况下,我希望它只需要对索引进行单个范围扫描,然后进行表访问。
select *
from (select *
from teste_rank r
where data_mov <= trunc(sysdate)
and codigo = 1
order by data_mov desc)
where rownum=1;