WITH BY与GROUP BY

时间:2018-04-10 16:16:09

标签: sql oracle performance where having

因此我被分配来估计两个查询的性能并得出令人惊讶的结果。我事先被告知HAVINGWHERE慢,因为它只在访问行后过滤结果。这似乎是合理的,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条记录
  • 访问行的成本为0.01毫秒(SELECT + COUNT)
  • 执行条款的成本是0.005毫秒
  • 有三种类型的客户状态,上面两种和“已故”
  • 有15 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 BYHAVING的行为相同,导致执行时间缩短了吗?

编辑:查询计划

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')

2 个答案:

答案 0 :(得分:2)

这就是事情:

  1. 根据Oracle执行计划,两个查询都在执行全表扫描。也就是说,他们正在阅读表中的 ALL THE ROWS 。没有区别。

  2. HAVING查询执行GROUP BY(散列),产生3行。然后,它将过滤器应用于这3行,并返回结果。

  3. WHERE查询在读取后将过滤器应用于每一行(规范中的100,000),将其减少到15,000。最后,它将这些(散列)分组为1行,并返回一行。

  4. 我认为在您描述的情况下,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;
/