it-swarm-eu.dev

Je absolutní výkon, je SUM rychlejší nebo COUNT?

To se týká počítání počtu záznamů, které odpovídají určité podmínce, např. invoice amount > $100.

Mám sklon dávat přednost

COUNT(CASE WHEN invoice_amount > 100 THEN 1 END)

To však platí stejně

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

Myslel bych, že COUNT je vhodnější ze 2 důvodů:

  1. Představuje záměr, kterým je COUNT
  2. COUNTpravděpodobně zahrnuje jednoduchý i += 1 operace někde, zatímco SUM nemůže počítat s tím, že jeho výraz bude jednoduchá celočíselná hodnota.

Má někdo konkrétní fakta o rozdílu na konkrétním RDBMS?

32
孔夫子

Většinou jste už na tuto otázku odpověděli sami. Musím přidat několik soustruhů:

V PostgreSQL (a dalších RDBMS, které podporují typ boolean) můžete přímo použít výsledek testu boolean. . Přenést do integer a SUM():

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

Nebo jej použijte ve výrazu NULLIF() a COUNT():

COUNT(NULLIF(amount > 100, FALSE))

Nebo s jednoduchým OR NULL:

COUNT(amount > 100 OR NULL)

Nebo různé jiné výrazy. Výkon je téměř identický . COUNT() je obvykle velmi mírně rychlejší než SUM(). Na rozdíl od SUM() a jako Paul již komentoval , COUNT() nikdy nevrací NULL, což může být výhodné. Příbuzný:

Protože Postgres 9.4 existuje také klauzule FILTER. Podrobnosti:

Je rychlejší než všechny výše uvedené, přibližně o 5 - 10%:

COUNT(*) FILTER (WHERE amount > 100)

Pokud je dotaz stejně jednoduchý jako váš testovací případ, pouze s jedním počtem a nic jiného, ​​můžete přepsat:

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

Což je skutečný král výkonu, i bez indexu.
S použitelným indexem může být rychlejší v řádu řádů, zejména u skenů pouze s indexem.

Benchmarky

Postgres 10

Provedl jsem novou sérii testů pro Postgres 10, včetně souhrnné klauzule FILTER a demonstrace role indexu pro malé i velké počty.

Jednoduché nastavení:

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

Skutečné časy se liší v závislosti na šumu pozadí a specifikách zkušebny. Zobrazeno typické nejlepší časy z větší sady testů. Tyto dva případy by měly zachytit podstatu:

Test 1 počítání ~ 1% všech řádků

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 <> housle zde

Test 2 počítání ~ 33% všech řádků

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 <> housle zde

Poslední test v každé sadě použil pouze indexové skenování, a proto pomohl spočítat jednu třetinu všech řádků. Obyčejné skenování indexů nebo bitmapových indexů nemůže konkurovat sekvenčnímu skenování, pokud zahrnuje zhruba 5% nebo více všech řádků.

Starý test na Postgres 9.1

Pro ověření jsem provedl rychlý test s EXPLAIN ANALYZE Na tabulce skutečného života v PostgreSQL 9.1.6.

74208 z 184568 řádků splňujících podmínky kat_id > 50. Všechny dotazy vrátí stejný výsledek. Každý z nich jsem běžel 10krát, abych vyloučil efekty ukládání do mezipaměti, a přidal jsem nejlepší výsledek jako poznámku:

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

Sotva žádný skutečný rozdíl ve výkonu.

33
Erwin Brandstetter

Toto je můj test na serveru 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

Při pohledu na jednotlivé běhy a dávky samostatně

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

Výsledky po provedení 5krát (a opakování) jsou zcela neprůkazné.

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

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

Ukazuje, že v provozních podmínkách je mnohem větší variabilita, než je rozdíl mezi implementací, pokud se měří granularitou časovače serveru SQL. Každá verze může přijít na vrchol a maximální rozptyl, který jsem kdy dostal, je 2,5%.

Avšak s odlišným přístupem:

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

Z mého čtení se zdá, že verze SUM dělá trochu víc. Provádí COUNT kromě a SUM. Řekl jsem, že COUNT(*) je odlišný a měl by být rychlejší než COUNT([Expr1004]) (přeskočit NULL, logičtější). Rozumný optimalizátor si uvědomí, že [Expr1004] V SUM([Expr1004]) ve verzi SUM je typu „int“, a proto využívají celočíselný registr.

V každém případě, i když stále věřím, že verze COUNT bude ve většině RDBMS rychlejší, můj závěr z testování je, že v budoucnu půjdu s SUM(.. 1.. 0..), přinejmenším pro SQL Server bez jiného důvodu, než jsou varování ANSI, která byla aktivována při použití COUNT.

11
孔夫子

Podle mých zkušeností Vytváření trasování pro obě metody v dotazu asi 10 000 000 jsem si všiml, že hrabě (*) používá asi dvakrát CPU a běží trochu rychleji. ale moje dotazy jsou bez filtru.

Počet (*)

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

Sum (1)

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