절대 성능을 위해 SUM이 더 빠르거나 COUNT입니까?


31

이는 특정 조건과 일치하는 레코드 수를 계산하는 것과 관련이 있습니다 (예 : invoice amount > $100 있습니다.

나는 선호하는 경향이있다

COUNT(CASE WHEN invoice_amount > 100 THEN 1 END)

그러나 이것은 유효합니다

SUM(CASE WHEN invoice_amount > 100 THEN 1 ELSE 0 END)

COUNT가 두 가지 이유로 선호된다고 생각했을 것입니다.

  1. 의도를 전달합니다. COUNT
  2. COUNT 아마 간단한 포함i += 1 SUM 간단한 정수 값으로 발현에 포함되지 수있는 반면, 조작 어딘가에.

누구든지 특정 RDBMS의 차이점에 대한 특정 사실이 있습니까?

답변:


32

당신은 대부분 스스로 질문에 스스로 대답했습니다. 추가해야 할 몇 가지 morsels가 있습니다.

에서 PostgreSQL을 (그리고 지원하는 다른 RDBMS boolean유형) 당신은 사용할 수 있습니다 boolean직접 테스트의 결과입니다. 그것을 캐스팅 integerSUM():

SUM((amount > 100)::int))

또는 NULLIF()표현식에 사용하십시오 COUNT().

COUNT(NULLIF(amount > 100, FALSE))

또는 간단한 OR NULL:

COUNT(amount > 100 OR NULL)

또는 다양한 다른 표현들. 성능은 거의 동일합니다 . COUNT()일반적으로보다 약간 빠릅니다 SUM(). 달리 SUM()와 같은 폴 이미 주석 , COUNT()결코 돌아 NULL편리 할 수 있습니다. 관련 :

Postgres 9.4FILTER 부터는 절도 있습니다. 세부:

그것은의 빠른 위의 모든보다 약 5로 - 10 % :

COUNT(*) FILTER (WHERE amount > 100)

경우 쿼리가 테스트 케이스로 간단하다, 단 하나의 수와 다른 아무것도, 당신은 다시 작성할 수 있습니다 :

SELECT count(*) FROM tbl WHERE amount > 100;

인덱스가 없어도 진정한 성능의 왕입니다.
적용 가능한 색인을 사용하면 특히 색인 전용 스캔의 경우 수십 배 더 빠를 수 있습니다.

벤치 마크

포스트그레스 10

골재를 포함하여 Postgres 10에 대한 새로운 일련의 테스트를 실행했습니다. FILTER 조항과 소규모 및 대규모 카운트의 역할이 포함됩니다.

간단한 설정 :

CREATE TABLE tbl (
   tbl_id int
 , amount int NOT NULL
);

INSERT INTO tbl
SELECT g, (random() * 150)::int
FROM   generate_series (1, 1000000) g;

-- only relevant for the last test
CREATE INDEX ON tbl (amount);

실제 시간은 배경 소음과 테스트 베드의 특성으로 인해 약간 다릅니다. 더 큰 테스트 세트에서 일반적인 최상의 시간을 표시 합니다. 이 두 경우는 본질을 포착해야합니다.

테스트 1 계산 ~ 모든 행의 1 %

SELECT COUNT(NULLIF(amount > 148, FALSE))            FROM tbl; -- 140 ms
SELECT SUM((amount > 148)::int)                      FROM tbl; -- 136 ms
SELECT SUM(CASE WHEN amount > 148 THEN 1 ELSE 0 END) FROM tbl; -- 133 ms
SELECT COUNT(CASE WHEN amount > 148 THEN 1 END)      FROM tbl; -- 130 ms
SELECT COUNT((amount > 148) OR NULL)                 FROM tbl; -- 130 ms
SELECT COUNT(*) FILTER (WHERE amount > 148)          FROM tbl; -- 118 ms -- !

SELECT count(*) FROM tbl WHERE amount > 148; -- without index  --  75 ms -- !!
SELECT count(*) FROM tbl WHERE amount > 148; -- with index     --   1.4 ms -- !!!

db <> 바이올린 여기

테스트 2 계수 ~ 모든 행의 33 %

SELECT COUNT(NULLIF(amount > 100, FALSE))            FROM tbl; -- 140 ms
SELECT SUM((amount > 100)::int)                      FROM tbl; -- 138 ms
SELECT SUM(CASE WHEN amount > 100 THEN 1 ELSE 0 END) FROM tbl; -- 139 ms
SELECT COUNT(CASE WHEN amount > 100 THEN 1 END)      FROM tbl; -- 138 ms
SELECT COUNT(amount > 100 OR NULL)                   FROM tbl; -- 137 ms
SELECT COUNT(*) FILTER (WHERE amount > 100)          FROM tbl; -- 132 ms -- !

SELECT count(*) FROM tbl WHERE amount > 100; -- without index  -- 102 ms -- !!
SELECT count(*) FROM tbl WHERE amount > 100; -- with index     --  55 ms -- !!!

db <> 바이올린 여기

각 세트의 마지막 테스트는 인덱스 전용 스캔을 사용 했기 때문에 모든 행의 1/3을 계산하는 데 도움이되었습니다. 일반 인덱스 또는 비트 맵 인덱스 스캔은 모든 행의 약 5 % 이상을 포함하는 경우 순차적 스캔과 경쟁 할 수 없습니다.

Postgres 9.1의 오래된 테스트

EXPLAIN ANALYZEPostgreSQL 9.1.6의 실제 테이블에서 빠른 테스트를 실행했는지 확인했습니다 .

조건으로 규정 된 184568 개의 행 중 74208 개 kat_id > 50. 모든 쿼리는 동일한 결과를 반환합니다. 캐싱 효과를 제외하고 각각 10 번씩 차례로 실행하여 최상의 결과를 메모로 추가했습니다.

SELECT SUM((kat_id > 50)::int)                      FROM log_kat; -- 438 ms
SELECT COUNT(NULLIF(kat_id > 50, FALSE))            FROM log_kat; -- 437 ms
SELECT COUNT(CASE WHEN kat_id > 50 THEN 1 END)      FROM log_kat; -- 437 ms
SELECT COUNT((kat_id > 50) OR NULL)                 FROM log_kat; -- 436 ms
SELECT SUM(CASE WHEN kat_id > 50 THEN 1 ELSE 0 END) FROM log_kat; -- 432 ms

실제 성능 차이는 거의 없습니다.


1
FILTER 솔루션이 "느린"그룹의 변형을 능가합니까?
Andriy M

@AndriyM : FILTER위의 표현식 (9.5 페이지 테스트)보다 집계 시간이 약간 더 빠릅니다 . 당신도 같은가요? (가능한 WHERE경우 여전히 성능의 왕입니다).
Erwin Brandstetter 1

PG를 가지고 있지 않으므로 알 수 없습니다. 어쨌든, 나는 단지 당신이 마지막 솔루션의 타이밍 수치로 답을 업데이트하기를 바랐습니다.)
Andriy M

@AndriyM : 마침내 새로운 벤치 마크를 추가했습니다. FILTER솔루션 입니다 빨리 내 테스트에서 일반적으로.
Erwin Brandstetter

11

이것은 SQL Server 2012 RTM에 대한 테스트입니다.

if object_id('tempdb..#temp1') is not null drop table #temp1;
if object_id('tempdb..#timer') is not null drop table #timer;
if object_id('tempdb..#bigtimer') is not null drop table #bigtimer;
GO

select a.*
into #temp1
from master..spt_values a
join master..spt_values b on b.type='p' and b.number < 1000;

alter table #temp1 add id int identity(10,20) primary key clustered;

create table #timer (
    id int identity primary key,
    which bit not null,
    started datetime2 not null,
    completed datetime2 not null,
);
create table #bigtimer (
    id int identity primary key,
    which bit not null,
    started datetime2 not null,
    completed datetime2 not null,
);
GO

--set ansi_warnings on;
set nocount on;
dbcc dropcleanbuffers with NO_INFOMSGS;
dbcc freeproccache with NO_INFOMSGS;
declare @bigstart datetime2;
declare @start datetime2, @dump bigint, @counter int;

set @bigstart = sysdatetime();
set @counter = 1;
while @counter <= 100
begin
    set @start = sysdatetime();
    select @dump = count(case when number < 100 then 1 end) from #temp1;
    insert #timer values (0, @start, sysdatetime());
    set @counter += 1;
end;
insert #bigtimer values (0, @bigstart, sysdatetime());
set nocount off;
GO

set nocount on;
dbcc dropcleanbuffers with NO_INFOMSGS;
dbcc freeproccache with NO_INFOMSGS;
declare @bigstart datetime2;
declare @start datetime2, @dump bigint, @counter int;

set @bigstart = sysdatetime();
set @counter = 1;
while @counter <= 100
begin
    set @start = sysdatetime();
    select @dump = SUM(case when number < 100 then 1 else 0 end) from #temp1;
    insert #timer values (1, @start, sysdatetime());
    set @counter += 1;
end;
insert #bigtimer values (1, @bigstart, sysdatetime());
set nocount off;
GO

개별 런 및 배치를 개별적으로보고

select which, min(datediff(mcs, started, completed)), max(datediff(mcs, started, completed)),
            avg(datediff(mcs, started, completed))
from #timer group by which
select which, min(datediff(mcs, started, completed)), max(datediff(mcs, started, completed)),
            avg(datediff(mcs, started, completed))
from #bigtimer group by which

5 회 실행 (및 반복) 한 결과는 매우 결정적입니다.

which                                       ** Individual
----- ----------- ----------- -----------
0     93600       187201      103927
1     93600       187201      103864

which                                       ** Batch
----- ----------- ----------- -----------
0     10108817    10545619    10398978
1     10327219    10498818    10386498

SQL Server 타이머의 세분성으로 측정 할 때 실행 조건에 차이가있는 것보다 실행 조건에 훨씬 더 많은 변동이 있음을 보여줍니다. 어느 버전이든 맨 위에 올 수 있으며 내가 가진 최대 분산은 2.5 %입니다.

그러나 다른 접근 방식을 취하십시오.

set showplan_text on;
GO
select SUM(case when number < 100 then 1 else 0 end) from #temp1;
select count(case when number < 100 then 1 end) from #temp1;

StmtText (SUM)

  |--Compute Scalar(DEFINE:([Expr1003]=CASE WHEN [Expr1011]=(0) THEN NULL ELSE [Expr1012] END))
       |--Stream Aggregate(DEFINE:([Expr1011]=Count(*), [Expr1012]=SUM([Expr1004])))
            |--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN [tempdb].[dbo].[#temp1].[number]<(100) THEN (1) ELSE (0) END))
                 |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[#temp1]))

StmtText (COUNT)

  |--Compute Scalar(DEFINE:([Expr1003]=CONVERT_IMPLICIT(int,[Expr1008],0)))
       |--Stream Aggregate(DEFINE:([Expr1008]=COUNT([Expr1004])))
            |--Compute Scalar(DEFINE:([Expr1004]=CASE WHEN [tempdb].[dbo].[#temp1].[number]<(100) THEN (1) ELSE NULL END))
                 |--Clustered Index Scan(OBJECT:([tempdb].[dbo].[#temp1]))

내가 읽은 바에 따르면 SUM 버전이 약간 더 많은 것으로 보입니다. SUM 외에 COUNT 수행하고 있습니다. 말했듯이, COUNT(*)다르며 COUNT([Expr1004])(NULL 건너 뛰기, 더 많은 논리) 보다 빠릅니다 . 합리적인 최적화는 것을 알게 될 것이다 [Expr1004]에서 SUM([Expr1004])합계 버전은 "INT"유형에 그래서 정수 레지스터를 사용합니다.

어쨌든, 여전히 COUNT대부분의 RDBMS에서 버전이 더 빠를 것이라고 생각하지만 테스트를 통해 얻은 결론 SUM(.. 1.. 0..)은 앞으로 SQL Server에 대해 사용할 때 ANSI 경고가 발생하는 것 외에 다른 이유는 없습니다. COUNT.


1

내 경험에 따르면 약 10,000,000의 쿼리에서 두 가지 방법 모두 추적을 수행하면 Count (*)가 CPU의 약 두 배를 사용하고 조금 더 빠르게 실행됩니다. 하지만 내 쿼리에는 필터가 없습니다.

카운트(*)

CPU...........: 1828   
Execution time:  470 ms  

합 (1)

CPU...........: 3859  
Execution time:  681 ms  

이 테스트에 사용한 RDBMS를 지정해야합니다.
EAmez
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.