在哪里与GROUP BY相比具有表现

Fel*_*lix 6 sql oracle performance where having

所以我被分配来评估两个查询的性能并得出了令人惊讶的结果.我之前被告知HAVING比较慢,WHERE因为它只在访问行后过滤结果.这似乎是合理的,这个关于SQL子句执行顺序的问题强调了这一点.

但是,我估计以下查询的性能有一些假设,似乎使用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
Run Code Online (Sandbox Code Playgroud)

假设是:

  • 该表CUSTOMER有100 000条记录
  • 访问行的成本是0.01ms(SELECT + COUNT)
  • 执行条款的成本是0.005毫秒
  • 有三种类型的客户状态,上面两个和'已故'
  • 有15000名"死者"客户

基于此,我的估计是:

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
Run Code Online (Sandbox Code Playgroud)

结果来自于这样一个事实: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')
Run Code Online (Sandbox Code Playgroud)

Jon*_*ler 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;
/
Run Code Online (Sandbox Code Playgroud)

循环运行代码可以看出,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;
/
Run Code Online (Sandbox Code Playgroud)