因此我被分配来估计两个查询的性能并得出令人惊讶的结果。我事先被告知HAVING
比WHERE
慢,因为它只在访问行后过滤结果。这似乎是合理的,this question on SQL clause execution order强调了这一点。
但是,我使用一些假设估计了以下查询的性能,并且似乎使用HAVING
执行实际上更快!
SELECT status, count(status)
FROM customer
GROUP BY status
HAVING status != 'Active' AND status != 'Dormant'
SELECT status, count(status)
FROM customer
WHERE status != 'Active' AND status != 'Dormant'
GROUP BY status
假设是:
CUSTOMER
有100 000条记录基于此我的估计是:
First query:
Accessing all rows, FROM: 100 000 * 0.01ms = 1000ms
GROUP BY: 100 000 * 0.005ms = 500ms
HAVING (2 conditions, 3 groups): 2 * 3 * 0.005ms = 0.03ms
SELECT and COUNT results: 15 000 * 0.01ms = 150ms
Total execution time: 1.65003s
Second query:
Accessing all the rows, FROM: 1000ms
WHERE: 2 * 100 000 * 0.005ms = 1000ms
GROUP BY: 15 000 * 0.005ms = 75ms
SELECT and COUNT results: 15 000 * 0.01ms = 150ms
Total execution time: 2.225s
结果来自于GROUP BY
只生成三个组,这些组非常容易过滤,而WHERE
必须逐个过滤并逐个过滤记录。
由于我天真地依赖权威,我假设我在某处犯了错误或提供的假设是错误的。
GROUP BY
与HAVING
的行为相同,导致执行时间缩短了吗?
PLAN_TABLE_OUTPUT /* With HAVING */
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 5 | 35 | 4 (25)| 00:00:01 |
|* 1 | FILTER | | | | | |
| 2 | HASH GROUP BY | | 5 | 35 | 4 (25)| 00:00:01 |
| 3 | TABLE ACCESS STORAGE FULL| CUSM | 5 | 35 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("STATUS"<>'Active' AND "STATUS"<>'Dormant')
PLAN_TABLE_OUTPUT /* With WHERE */
-----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 7 | 4 (25)| 00:00:01 |
| 1 | HASH GROUP BY | | 1 | 7 | 4 (25)| 00:00:01 |
|* 2 | TABLE ACCESS STORAGE FULL| CUSM | 1 | 7 | 3 (0)| 00:00:01 |
-----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - storage("STATUS"<>'Active' AND "STATUS"<>'Dormant')
filter("STATUS"<>'Active' AND "STATUS"<>'Dormant')
答案 0 :(得分:2)
这就是事情:
根据Oracle执行计划,两个查询都在执行全表扫描。也就是说,他们正在阅读表中的 ALL THE ROWS 。没有区别。
HAVING查询执行GROUP BY(散列),产生3行。然后,它将过滤器应用于这3行,并返回结果。
WHERE查询在读取后将过滤器应用于每一行(规范中的100,000),将其减少到15,000。最后,它将这些(散列)分组为1行,并返回一行。
我认为在您描述的情况下,WHERE查询将过滤器应用于所有100,000行,而HAVING查询推迟过滤器并仅将其应用于3行。这使得HAVING查询更快。
现在,不要假设此结果将适用于您拥有的每个查询。 Oracle非常聪明地使用表统计信息。根据您添加到表中的实际数据,该计划将在未来发生变化。具有5行的计划决不代表100,000行的计划。
用一粒盐取这个结果。现实场景更加棘手。
答案 1 :(得分:2)
您的一个假设是错误的: HAVING比WHERE慢,因为它只在访问和散列行后过滤结果。
散布部分使得HAVING条件比WHERE条件更昂贵。散列需要写入数据,这在物理和算法上都会更昂贵。
散列需要书写和阅读数据。理想情况下,散列数据将在O(n)
时间内运行。但实际上会出现哈希冲突,这会减慢速度。实际上,并非所有数据都适合内存。
这两个问题可能是灾难性的。在最坏的情况下,在内存有限的情况下,散列需要多次通过,复杂度接近O(n^2)
。在临时表空间中写入磁盘比写入内存慢几个数量级。
这些是您需要担心数据库的性能问题。与读取,写入和连接数据的时间相比,运行简单条件和表达式的常量时间通常无关紧要。
在您的环境中尤其如此。操作TABLE ACCESS STORAGE FULL
表示您正在使用Exadata。根据平台的不同,您可能会在硅片中利用SQL。这些高级条件可以完美地转换为在存储设备上执行的低级指令。这意味着您对执行条款的成本估计可能会高出几个数量级。
创建一个包含100,000行的示例表:
create table customer(id number, status varchar2(100));
insert into customer
select
level,
case
when level <= 15000 then 'Deceased'
when level between 15001 and 50001 then 'Active'
else 'Dormant'
end
from dual
connect by level <= 100000;
begin
dbms_stats.gather_table_stats(user, 'customer');
end;
/
在循环中运行代码表明WHERE
版本的速度大约是HAVING
版本的两倍。
--Run times (in seconds): 0.765, 0.78, 0.765
declare
type string_nt is table of varchar2(100);
type number_nt is table of number;
v_status string_nt;
v_count number_nt;
begin
for i in 1 .. 100 loop
SELECT status, count(status)
bulk collect into v_status, v_count
FROM customer
GROUP BY status
HAVING status != 'Active' AND status != 'Dormant';
end loop;
end;
/
--Run times (in seconds): 0.39, 0.39, 0.39
declare
type string_nt is table of varchar2(100);
type number_nt is table of number;
v_status string_nt;
v_count number_nt;
begin
for i in 1 .. 100 loop
SELECT status, count(status)
bulk collect into v_status, v_count
FROM customer
WHERE status != 'Active' AND status != 'Dormant'
GROUP BY status;
end loop;
end;
/