Mutlak performans için, SUM daha hızlı mı yoksa COUNT?


31

Bu, örneğin, belirli bir koşulla eşleşen kayıt sayısının sayılması ile ilgilidir invoice amount > $100.

Tercih etme eğilimindeyim

COUNT(CASE WHEN invoice_amount > 100 THEN 1 END)

Ancak, bu kadar geçerli

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

COUNT'ın 2 sebepten dolayı tercih edilebilir olduğunu düşünürdüm:

  1. Olan niyeti taşır COUNT
  2. COUNT Muhtemelen bir i += 1yerde basit bir işlem içerir , oysa SUM basit bir tamsayı olarak ifade edilmesine güvenemez.

Belirli RDBMS üzerindeki fark hakkında belirli gerçekleri olan var mı?

Yanıtlar:


32

Soruyu çoğunlukla kendin cevapladın. Eklemek için birkaç morsel var:

In PostgreSQL (ve destekleyen diğer RDBMS booleantürü) kullanabilirsiniz booleandoğrudan testin sonucunu. Bunu için Cast integerve SUM():

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

Veya bir NULLIF()ifadede kullanın ve COUNT():

COUNT(NULLIF(amount > 100, FALSE))

Veya basit OR NULL:

COUNT(amount > 100 OR NULL)

Veya çeşitli diğer ifadeler. Performans neredeyse aynı . COUNT()genellikle çok daha hızlıdır SUM(). Paul'ün yorumladığıSUM() gibi ve aksine , asla geri dönüşü olmaz , bu uygun olabilir. İlgili:COUNT()NULL

Postgres 9.4'tenFILTER beri de bir fıkra var . Detaylar:

Bu var daha hızlı yukarıdaki hepsinden daha 5 civarında tarafından -% 10:

COUNT(*) FILTER (WHERE amount > 100)

Eğer sorgu test vaka olarak basit olarak, sadece tek bir sayımı ve başka bir şey, sen yeniden yazabilirsiniz:

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

Performansın gerçek kralı olan bu, endekssiz bile.
Uygulanabilir bir indeks ile özellikle sadece indeks taramaları ile büyüklük sırasına göre daha hızlı olabilir.

Deneyler

Kartpostallar 10

Toplam FILTERmadde ve küçük ve büyük sayımlar için bir endeksin rolünü gösteren, Postgres 10 için yeni bir dizi test yaptım .

Basit kurulum:

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

Gerçek zamanlar arkaplan gürültüsü ve test yatağının özellikleri nedeniyle biraz değişebilir. Daha büyük bir test kümesinden tipik en iyi zamanlar gösteriliyor. Bu iki dava özü yakalamalıdır:

Test 1 sayımı ~ tüm satırların% 1'i

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 <> burada keman

Test 2 sayımı ~ tüm satırların% 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 <> burada keman

Her setteki son test sadece indeksli bir tarama kullandı, bu yüzden tüm satırların üçte birini saymaya yardımcı oldu. Düz dizin veya bitmap dizin taramaları, tüm satırların kabaca% 5'ini veya daha fazlasını içerdiğinde sıralı bir tarama ile rekabet edemez.

Postgres 9.1 için eski test

Doğrulamak için EXPLAIN ANALYZEPostgreSQL 9.1.6'daki gerçek yaşam tablosunda hızlı bir test yaptım .

74208/184568 satır durumu ile nitelikli kat_id > 50. Tüm sorgular aynı sonucu verir. Önbellekleme efektlerini hariç tutmak için her birini sırasıyla 10 kez çalıştırdım ve en iyi sonucu not olarak ekledik:

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

Performansta neredeyse hiçbir gerçek fark yok.


1
FİLTRE çözümü "yavaş" grubundaki değişikliklerden herhangi birini geçiyor mu?
Andriy M

@AndriyM: Agrega için FILTERyukarıdaki ifadelere göre biraz daha hızlı zamanlar görüyorum (pg 9.5 ile test). Aynısını anladın mı? ( WHEREhala performansın kralıdır - mümkünse).
Erwin Brandstetter

Kullanışlı bir PG almadım, söyleyemem. Her neyse, cevabınızı son çözümün zamanlama rakamlarıyla güncellemenizi umuyordum, sadece eksiksizlik için :)
Andriy M

@AndriyM: Sonunda yeni kriterler eklemek için vardım. FILTERÇözüm ise daha hızlı benim testlerde genellikle.
Erwin Brandstetter

11

Bu benim SQL Server 2012 RTM'deki sınavım.

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

Bireysel koşulara ve partilere ayrı ayrı bakmak

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 kez çalıştırdıktan sonra (ve tekrarlayan) sonuçlar oldukça yetersizdir.

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

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

Çalışma koşullarında, SQL Server zamanlayıcısının ayrıntı derecesine göre ölçüldüğünde uygulama arasında farklılık olduğundan çok daha fazla değişkenlik olduğunu gösterir. Her iki sürüm de üst sıralarda olabilir ve sahip olduğum maksimum varyans% 2,5'tir.

Ancak, farklı bir yaklaşım benimsemek:

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

Benim okumaya göre, SUM versiyonunun biraz daha fazlasını yaptığı anlaşılıyor. TOPLA ek olarak bir COUNT gerçekleştiriyor . Bunu söyledikten sonra COUNT(*)farklıdır ve daha hızlı olması gerekir COUNT([Expr1004])(NULL atla, daha fazla mantık). Makul iyileştirici fark edecektir [Expr1004]içindeSUM([Expr1004]) SUM sürümü "int" tipi olduğu ve bu yüzden bir tamsayı kayıt kullanmaktadır.

Her durumda, COUNTsürümün çoğu RDBMS'de daha hızlı olacağına inanıyorum, ancak testten elde ettiğim sonuç SUM(.. 1.. 0..), gelecekte en azından SQL Server için ANSI UYARILARINI kullanırken ortaya çıkan herhangi bir sebep olmadan sebep olacağım. COUNT.


1

Deneyimime göre, yaklaşık 10.000.000'lik bir Sorgudaki her iki yöntem için bir iz yapma, Sayı'nın (*) yaklaşık iki kat daha fazla CPU kullandığını ve biraz daha hızlı çalıştığını fark ettim. ama benim sorgularım filtre olmadan.

(*) Sayısı

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

Toplam (1)

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

Bu testi yapmak için hangi RDBMS'yi kullandığınızı belirtmelisiniz.
EAmez
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.