postgresql COUNT (DISTINCT ...) sangat lambat

166

Saya punya permintaan SQL yang sangat sederhana:

SELECT COUNT(DISTINCT x) FROM table;

Meja saya memiliki sekitar 1,5 juta baris. Permintaan ini berjalan sangat lambat; dibutuhkan sekitar 7,5, dibandingkan dengan

 SELECT COUNT(x) FROM table;

yang memakan waktu sekitar 435 ms. Apakah ada cara untuk mengubah kueri saya untuk meningkatkan kinerja? Saya sudah mencoba mengelompokkan dan melakukan penghitungan reguler, serta menempatkan indeks pada x; keduanya memiliki waktu eksekusi 7,5s yang sama.

ferson2020
sumber
Saya kira tidak. Mendapatkan nilai berbeda dari 1,5 juta baris hanya akan menjadi lambat.
Ry-
5
Saya baru saja mencobanya di C #, mendapatkan nilai berbeda dari 1,5 juta integer dari memori membutuhkan lebih dari satu detik di komputer saya. Jadi saya pikir Anda mungkin kurang beruntung.
Ry-
Rencana kueri akan sangat tergantung pada struktur tabel (indeks) dan pengaturan konstanta tuning (kerja), efektif_cache_size, random_page_cost). Dengan penyetelan yang wajar, kueri mungkin dapat dieksekusi dalam waktu kurang dari satu detik.
wildplasser
Bisakah kamu lebih spesifik? Apa indeks dan konstanta tuning yang diperlukan untuk mendapatkannya di bawah satu detik? Untuk kesederhanaan, anggap ini adalah tabel dua kolom dengan kunci utama pada kolom pertama y, dan saya melakukan kueri 'berbeda' ini pada kolom kedua x tipe int, dengan 1,5 juta baris.
ferson2020
1
Tolong, sertakan definisi tabel dengan semua indeks ( \doutput dari psqlyang baik) dan tepat kolom yang bermasalah dengan Anda. Akan baik untuk melihat EXPLAIN ANALYZEkedua pertanyaan.
vyegorov

Jawaban:

316

Anda bisa menggunakan ini:

SELECT COUNT(*) FROM (SELECT DISTINCT column_name FROM table_name) AS temp;

Ini jauh lebih cepat daripada:

COUNT(DISTINCT column_name)
Ankur
sumber
38
pertanyaan suci batman! Ini mempercepat hitungan postgres saya yang berbeda dari tahun 190an menjadi 4,5 whoa!
rogerdpack
20
Saya menemukan utas ini di www.postgresql.org yang membahas hal yang sama: tautan . Salah satu balasan (oleh Jeff Janes) mengatakan bahwa COUNT (DISTINCT ()) mengurutkan tabel untuk melakukan tugasnya alih-alih menggunakan hash.
Ankur
5
@Ankur Boleh saya bertanya? Karena COUNT(DISTINCT())melakukan penyortiran, akan sangat membantu untuk memiliki indeks pada column_nameterutama dengan jumlah yang relatif kecil work_mem(di mana hashing akan menghasilkan jumlah batch yang relatif besar). Karena itu, tidak selalu buruk untuk menggunakan COUNT (DISTINCT () _, bukan?
St.Antario
2
@musmahn Count(column)hanya menghitung nilai yang bukan nol. count(*)menghitung baris. Jadi yang pertama / lebih lama, juga akan menghitung baris nol (sekali). Ubah untuk count(column_name)membuat mereka berperilaku sama.
GolezTrol
1
@ ASur ini tidak banyak berguna bagi saya .. tidak mendapatkan perbaikan yang luar biasa.
Shiwangini
11
-- My default settings (this is basically a single-session machine, so work_mem is pretty high)
SET effective_cache_size='2048MB';
SET work_mem='16MB';

\echo original
EXPLAIN ANALYZE
SELECT
        COUNT (distinct val) as aantal
FROM one
        ;

\echo group by+count(*)
EXPLAIN ANALYZE
SELECT
        distinct val
       -- , COUNT(*)
FROM one
GROUP BY val;

\echo with CTE
EXPLAIN ANALYZE
WITH agg AS (
    SELECT distinct val
    FROM one
    GROUP BY val
    )
SELECT COUNT (*) as aantal
FROM agg
        ;

Hasil:

original                                                      QUERY PLAN                                                      
----------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36448.06..36448.07 rows=1 width=4) (actual time=1766.472..1766.472 rows=1 loops=1)
   ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=31.371..185.914 rows=1499845 loops=1)
 Total runtime: 1766.642 ms
(3 rows)

group by+count(*)
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=412.470..412.598 rows=1300 loops=1)
   ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=412.066..412.203 rows=1300 loops=1)
         ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=26.134..166.846 rows=1499845 loops=1)
 Total runtime: 412.686 ms
(4 rows)

with CTE
                                                             QUERY PLAN                                                             
------------------------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=36506.56..36506.57 rows=1 width=0) (actual time=408.239..408.239 rows=1 loops=1)
   CTE agg
     ->  HashAggregate  (cost=36464.31..36477.31 rows=1300 width=4) (actual time=407.704..407.847 rows=1300 loops=1)
           ->  HashAggregate  (cost=36448.06..36461.06 rows=1300 width=4) (actual time=407.320..407.467 rows=1300 loops=1)
                 ->  Seq Scan on one  (cost=0.00..32698.45 rows=1499845 width=4) (actual time=24.321..165.256 rows=1499845 loops=1)
       ->  CTE Scan on agg  (cost=0.00..26.00 rows=1300 width=0) (actual time=407.707..408.154 rows=1300 loops=1)
     Total runtime: 408.300 ms
    (7 rows)

Rencana yang sama dengan CTE mungkin juga dapat diproduksi dengan metode lain (fungsi jendela)

wildplasser
sumber
2
Sudahkah Anda mempertimbangkan efek caching? Jika melakukan tiga "jelaskan analisis" selanjutnya, yang pertama mungkin lambat mengambil sesuatu dari disk sementara dua yang terakhir mungkin cepat mengambil dari memori.
tobixen
Memang: effective_cache_size adalah pengaturan pertama untuk mengubah. Milik saya 2GB, IIRC.
wildplasser
Saya menetapkan efektif_cache_size saya ke 2GB, tanpa perubahan kinerja. Adakah pengaturan lain yang Anda sarankan untuk diubah? Jika ya, untuk apa?
ferson2020
1) bagaimana Anda mengaturnya? (Apakah Anda HUP itu?) 2) Apakah Anda benar-benar memiliki banyak memori yang tersedia? 3) tunjukkan rencana Anda kepada kami. 4) mungkin mesin saya lebih cepat, atau Anda memiliki beban lebih bersamaan untuk ditangani. @ ferson2020: Oke
wildplasser
Saya mengaturnya dengan pernyataan: SET effective_cache_size = '2GB'; Saya punya banyak memori yang tersedia. Saya mencoba memasukkan rencana permintaan saya, tetapi tidak cocok di kotak komentar.
ferson2020
2

Jika Anda count(distinct(x))secara signifikan lebih lambat dari count(x)itu, Anda bisa mempercepat kueri ini dengan mempertahankan jumlah nilai x dalam tabel yang berbeda, misalnya table_name_x_counts (x integer not null, x_count int not null), menggunakan pemicu. Tetapi kinerja penulisan Anda akan menurun dan jika Anda memperbarui beberapa xnilai dalam satu transaksi maka Anda harus melakukan ini dalam urutan eksplisit untuk menghindari kemungkinan kebuntuan.

Tometzky
sumber
0

Saya juga mencari jawaban yang sama, karena pada suatu saat saya membutuhkan total_count dengan nilai yang berbeda bersama dengan limit / offset .

Karena agak sulit untuk dilakukan- Untuk mendapatkan jumlah total dengan nilai yang berbeda bersama dengan batas / offset. Biasanya sulit untuk mendapatkan jumlah total dengan batas / offset. Akhirnya saya mendapatkan cara untuk melakukan -

SELECT DISTINCT COUNT(*) OVER() as total_count, * FROM table_name limit 2 offset 0;

Performa permintaan juga tinggi.

Rana Pratap Singh
sumber