Büyük PostgresSQL tablosunda COUNT / GROUP-BY performansını artırın?


24

PostgresSQL 9.2 kullanıyorum ve yaklaşık 6.700.000 satırla 12 sütun ilişkisine sahibim. Her biri bir kullanıcıya (onu yaratan) referans veren 3B alanda düğümler içerir. Hangi kullanıcının kaç tane düğüm yarattığını sorgulamak için aşağıdakileri yapıyorum ( explain analyzedaha fazla bilgi için eklendi ):

EXPLAIN ANALYZE SELECT user_id, count(user_id) FROM treenode WHERE project_id=1 GROUP BY user_id;
                                                    QUERY PLAN                                                         
---------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=253668.70..253669.07 rows=37 width=8) (actual time=1747.620..1747.623 rows=38 loops=1)
   ->  Seq Scan on treenode  (cost=0.00..220278.79 rows=6677983 width=8) (actual time=0.019..886.803 rows=6677983 loops=1)
         Filter: (project_id = 1)
 Total runtime: 1747.653 ms

Gördüğünüz gibi, bu işlem yaklaşık 1.7 saniye sürüyor. Veri miktarı düşünüldüğünde bu fena değil, ama bunun iyileştirilip geliştirilemeyeceğini merak ediyorum. Kullanıcı sütununa bir BTree dizini eklemeye çalıştım, ancak bu hiçbir şekilde yardımcı olmadı.

Alternatif önerileriniz var mı?


Bütünlük uğruna, bu, tüm endeksleriyle (yabancı anahtar kısıtlamaları, referanslar ve tetikleyiciler olmadan) tam tablo tanımıdır:

    Column     |           Type           |                      Modifiers                    
---------------+--------------------------+------------------------------------------------------
 id            | bigint                   | not null default nextval('concept_id_seq'::regclass)
 user_id       | bigint                   | not null
 creation_time | timestamp with time zone | not null default now()
 edition_time  | timestamp with time zone | not null default now()
 project_id    | bigint                   | not null
 location      | double3d                 | not null
 reviewer_id   | integer                  | not null default (-1)
 review_time   | timestamp with time zone |
 editor_id     | integer                  |
 parent_id     | bigint                   |
 radius        | double precision         | not null default 0
 confidence    | integer                  | not null default 5
 skeleton_id   | bigint                   |
Indexes:
    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE CONSTRAINT, btree (id)
    "skeleton_id_treenode_index" btree (skeleton_id)
    "treenode_editor_index" btree (editor_id)
    "treenode_location_x_index" btree (((location).x))
    "treenode_location_y_index" btree (((location).y))
    "treenode_location_z_index" btree (((location).z))
    "treenode_parent_id" btree (parent_id)
    "treenode_user_index" btree (user_id)

Düzenleme: Bu, @ypercube tarafından önerilen sorguyu (ve dizini) kullandığımda ortaya çıkan sonuçtur (sorgu olmadan yaklaşık 5,3 saniye sürer EXPLAIN ANALYZE):

EXPLAIN ANALYZE SELECT u.id, ( SELECT COUNT(*) FROM treenode AS t WHERE t.project_id=1 AND t.user_id = u.id ) AS number_of_nodes FROM auth_user As u;
                                                                        QUERY PLAN                                                                     
----------------------------------------------------------------------------------------------------------------------------------------------------------
 Seq Scan on auth_user u  (cost=0.00..6987937.85 rows=46 width=4) (actual time=29.934..5556.147 rows=46 loops=1)
   SubPlan 1
     ->  Aggregate  (cost=151911.65..151911.66 rows=1 width=0) (actual time=120.780..120.780 rows=1 loops=46)
           ->  Bitmap Heap Scan on treenode t  (cost=4634.41..151460.44 rows=180486 width=0) (actual time=13.785..114.021 rows=145174 loops=46)
                 Recheck Cond: ((project_id = 1) AND (user_id = u.id))
                 Rows Removed by Index Recheck: 461076
                 ->  Bitmap Index Scan on treenode_user_index  (cost=0.00..4589.29 rows=180486 width=0) (actual time=13.082..13.082 rows=145174 loops=46)
                       Index Cond: ((project_id = 1) AND (user_id = u.id))
 Total runtime: 5556.190 ms
(9 rows)

Time: 5556.804 ms

Düzenleme 2: Bu, @ erwin-brandstetter'ın önerdiği şekilde bir indexaçık project_id, user_id(ancak henüz şema optimizasyonu yok) kullandığımda ortaya çıkan sonuçtur (sorgu, orijinal sorgunla aynı hızda 1.5 saniye boyunca çalışır):

EXPLAIN ANALYZE SELECT user_id, count(user_id) as ct FROM treenode WHERE project_id=1 GROUP BY user_id;
                                                        QUERY PLAN                                                      
---------------------------------------------------------------------------------------------------------------------------
 HashAggregate  (cost=253670.88..253671.24 rows=37 width=8) (actual time=1807.334..1807.339 rows=38 loops=1)
   ->  Seq Scan on treenode  (cost=0.00..220280.62 rows=6678050 width=8) (actual time=0.183..893.491 rows=6678050 loops=1)
         Filter: (project_id = 1)
 Total runtime: 1807.368 ms
(4 rows)

Ayrıca bir tablo var mı Usersile user_idbirincil anahtar olarak?
ypercubeᵀᴹ

Postgres için üçüncü parti bir sütun mağazası eklentisi olduğunu gördüm. Ayrıca, sadece yeni ios uygulamasından mesaj göndermek istedim
swasheck

2
İyi, anlaşılır, eksiksiz bir soru için teşekkürler - sürümler, tablo tanımları, vb.
Craig Ringer

@ypercube Evet, bir kullanıcı tablosu var.
tomka

Kaç tane farklı project_idve user_id? Tablo sürekli güncelleniyor mu yoksa somutlaştırılmış bir görünümle (bir süre için) çalışabilir misiniz?
Erwin Brandstetter

Yanıtlar:


25

Asıl sorun eksik indeks. Fakat dahası var.

SELECT user_id, count(*) AS ct
FROM   treenode
WHERE  project_id = 1
GROUP  BY user_id;
  • Çok fazla bigintsütununuz var. Muhtemelen fazla abartılı. Genellikle, ve integergibi sütunlar için fazlasıyla yeterli olur . Bu, bir sonraki öğeye de yardımcı olacaktır. Tablo tanımını optimize ederken, veri hizalama ve doldurmaya vurgu yaparak bu ilgili cevabı düşünün . Ancak gerisinin çoğu da geçerlidir:project_iduser_id

  • Odadaki fil : hayır yoktur üzerindeki endeksproject_id . Bir tane yarat. Bu, bu cevabın geri kalanından daha önemlidir.
    Bunu yaparken, bunu çok noktalı bir dizin yapın:

    CREATE INDEX treenode_project_id_user_id_index ON treenode (project_id, user_id);

    Tavsiyeme uyduysanız, integerburada mükemmel olurdu:

  • user_idtanımlanır NOT NULL, yani count(user_id)eşdeğerdir count(*), ancak ikincisi biraz daha kısa ve daha hızlıdır. (Bu özel sorguda, bu user_idtanımlanmadan bile geçerli olacaktır NOT NULL.)

  • idZaten birincil anahtardır, ek UNIQUEkısıtlama işe yaramazdır . Bırak:

    "treenode_pkey" PRIMARY KEY, btree (id)
    "treenode_id_key" UNIQUE CONSTRAINT, btree (id)

    Bir kenara: Ben idsütun ismi olarak kullanmazdım . Açıklayıcı bir şey kullanın treenode_id.

Bilgi eklendi

S: How many different project_id and user_id?
A: not more than five different project_id.

Bu, Postgres'in sorgunuzu karşılamak için tüm tablonun yaklaşık% 20'sini okuması gerektiği anlamına gelir . Yalnızca dizin taraması kullanmıyorsa , tablodaki sıralı tarama herhangi bir dizini içermekten daha hızlı olacaktır. Burada kazanmak için daha fazla performans yok - tablo ve sunucu ayarlarını optimize etmek dışında.

Yalnızca dizin taramasına gelince : Bunun ne kadar etkili olabileceğini görmek için, VACUUM ANALYZEbütçenizi karşılayabiliyorsanız çalıştırın (yalnızca tabloyu kilitler). Ardından sorgunuzu tekrar deneyin. Şimdi sadece endeks kullanılarak orta derecede hızlı olması gerekir . Önce bu ilgili cevabı okuyun:

Postgres 9.6 ve yalnızca indeks taramalarında Postgres Wiki ile eklenen manuel sayfanın yanı sıra .


1
Erwin, önerileriniz için teşekkürler. Haklısınız, için user_idve project_id integerfazlasıyla yeterli olmalıdır. Burada yaklaşık 70ms'lik tasarruf count(*)yerine kullanmak count(user_id)biliniyor. İlk yayına önerinizi ekledikten EXPLAIN ANALYZEsonra sorguyu ekledim index. Yine de performansı iyileştirmiyor (ama aynı zamanda acı vermiyor). Hiç kullanılmamış gibi görünüyor index. Şema optimizasyonlarını yakında test edeceğim.
tomka

1
Devre dışı bırakırsam seqscan, dizin kullanılır ( Index Only Scan using treenode_project_id_user_id_index on treenode), ancak sorgu yaklaşık 2,5 saniye sürer (bu, seqscan'dan 1 saniye daha uzun olur).
tomka

1
Teşekkürler güncellemelerin için. Bu eksik bitler sorumun bir parçası olmalıydı, bu doğru. Sadece etkilerinin farkında değildim. Şemamı önerdiğin gibi optimize edeceğim --- haydi ondan ne kazanabileceğime bir bakalım. Açıklaman için teşekkür ederim, bana mantıklı geliyor ve bu yüzden cevabını kabul edilen olarak işaretleyeceğim.
tomka

7

Önce bir dizin eklerdim, (project_id, user_id)sonra 9.3 sürümünde şu sorguyu deneyin:

SELECT u.user_id, c.number_of_nodes 
FROM users AS u
   , LATERAL
     ( SELECT COUNT(*) AS number_of_nodes 
       FROM treenode AS t
       WHERE t.project_id = 1 
         AND t.user_id = u.user_id
     ) c 
-- WHERE c.number_of_nodes > 0 ;   -- you probably want this as well
                                   -- to show only relevant users

9.2'de şunu deneyin:

SELECT u.user_id, 
       ( SELECT COUNT(*) 
         FROM treenode AS t
         WHERE t.project_id = 1 
           AND t.user_id = u.user_id
       ) AS number_of_nodes  
FROM users AS u ;

Bir usersmasanız olduğunu varsayıyorum . Eğer değilse, ile değiştirin users:
(SELECT DISTINCT user_id FROM treenode)


Cevabınız için çok teşekkür ederim. Haklısın, bir kullanıcı tablosu var. Bununla birlikte, sorgunuzu 9.2'de kullanarak, dizinin oluşturulup oluşturulmadığına bakılmaksızın sonucu elde etmek yaklaşık 5 saniye sürer. Dizini şu şekilde yarattım:, CREATE INDEX treenode_user_index ON treenode USING btree (project_id, user_id);ancak USINGcümlecik olmadan da denedim . Bir şey mi özledim?
tomka

Kaç satır içinde vardır userssorgu dönüş (orada olması için kaç kullanıcı olduğunu mu masa ve kaç satır project_id=1)? Dizini ekledikten sonra bu sorgunun açıklamasını gösterebilir misiniz?
ypercubeᵀᴹ

1
İlk olarak, ilk yorumumda yanılmışım. Önerilen dizininiz olmadan sonucu almak yaklaşık 40s (!) Sürer. indexYerinde ile yaklaşık 5 saniye sürer . Karışıklık için özür dilerim. Benim içinde usersmasaya ben 46 girdileri var. Sorgu yalnızca 9 satır döndürür. Şaşırtıcı bir şekilde, SELECT DISTINCT user_id FROM treenode WHERE project_id=1;38 satır döndürür. explainİlk gönderime ekledim . Ve karışıklığı önlemek için: usersmasam aslında denir auth_user.
tomka

SELECT DISTINCT user_id FROM treenode WHERE project_id=1;Sorgu sadece 9 döndürürken nasıl 38 satır döndürür merak ediyorum .
ypercubeᵀᴹ

Bunu deneyebilir misin ?:SET enable_seqscan = OFF; (Query); SET enable_seqscan = ON;
ypercubeᵀᴹ
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.