Çok yavaş basit bir katılma sorgusu


12

Basit DB yapısı (çevrimiçi bir forum için):

CREATE TABLE users (
    id integer NOT NULL PRIMARY KEY,
    username text
);
CREATE INDEX ON users (username);

CREATE TABLE posts (
    id integer NOT NULL PRIMARY KEY,
    thread_id integer NOT NULL REFERENCES threads (id),
    user_id integer NOT NULL REFERENCES users (id),
    date timestamp without time zone NOT NULL,
    content text
);
CREATE INDEX ON posts (thread_id);
CREATE INDEX ON posts (user_id);

Tablolarda yaklaşık 80 bin giriş usersve 2,6 milyon giriş posts. İlk 100 kullanıcıyı gönderilerine göre almak için bu basit sorgu 2,4 saniye sürer :

EXPLAIN ANALYZE SELECT u.id, u.username, COUNT(p.id) AS PostCount FROM users u
                    INNER JOIN posts p on p.user_id = u.id
                    WHERE u.username IS NOT NULL
                    GROUP BY u.id
ORDER BY PostCount DESC LIMIT 100;
Limit  (cost=316926.14..316926.39 rows=100 width=20) (actual time=2326.812..2326.830 rows=100 loops=1)
  ->  Sort  (cost=316926.14..317014.83 rows=35476 width=20) (actual time=2326.809..2326.820 rows=100 loops=1)
        Sort Key: (count(p.id)) DESC
        Sort Method: top-N heapsort  Memory: 32kB
        ->  HashAggregate  (cost=315215.51..315570.27 rows=35476 width=20) (actual time=2311.296..2321.739 rows=34608 loops=1)
              Group Key: u.id
              ->  Hash Join  (cost=1176.89..308201.88 rows=1402727 width=16) (actual time=16.538..1784.546 rows=1910831 loops=1)
                    Hash Cond: (p.user_id = u.id)
                    ->  Seq Scan on posts p  (cost=0.00..286185.34 rows=1816634 width=8) (actual time=0.103..1144.681 rows=2173916 loops=1)
                    ->  Hash  (cost=733.44..733.44 rows=35476 width=12) (actual time=15.763..15.763 rows=34609 loops=1)
                          Buckets: 65536  Batches: 1  Memory Usage: 2021kB
                          ->  Seq Scan on users u  (cost=0.00..733.44 rows=35476 width=12) (actual time=0.033..6.521 rows=34609 loops=1)
                                Filter: (username IS NOT NULL)
                                Rows Removed by Filter: 11335

Execution time: 2301.357 ms

Daha set enable_seqscan = falseda kötüsü:

Limit  (cost=1160881.74..1160881.99 rows=100 width=20) (actual time=2758.086..2758.107 rows=100 loops=1)
  ->  Sort  (cost=1160881.74..1160970.43 rows=35476 width=20) (actual time=2758.084..2758.098 rows=100 loops=1)
        Sort Key: (count(p.id)) DESC
        Sort Method: top-N heapsort  Memory: 32kB
        ->  GroupAggregate  (cost=0.79..1159525.87 rows=35476 width=20) (actual time=0.095..2749.859 rows=34608 loops=1)
              Group Key: u.id
              ->  Merge Join  (cost=0.79..1152157.48 rows=1402727 width=16) (actual time=0.036..2537.064 rows=1910831 loops=1)
                    Merge Cond: (u.id = p.user_id)
                    ->  Index Scan using users_pkey on users u  (cost=0.29..2404.83 rows=35476 width=12) (actual time=0.016..41.163 rows=34609 loops=1)
                          Filter: (username IS NOT NULL)
                          Rows Removed by Filter: 11335
                    ->  Index Scan using posts_user_id_index on posts p  (cost=0.43..1131472.19 rows=1816634 width=8) (actual time=0.012..2191.856 rows=2173916 loops=1)
Planning time: 1.281 ms
Execution time: 2758.187 ms

Grup username, Postgres'te eksik, çünkü gerekli değil (SQL Server, usernamekullanıcı adını seçmek istiyorsam, gruplandırmam gerektiğini söylüyor ). usernameİle gruplama Postgres üzerinde yürütme süresine biraz ms ekler veya hiçbir şey yapmaz.

Bilim için, Microsoft SQL Server'ı aynı sunucuya (archlinux, 8 çekirdek xeon, 24 gb ram, ssd) yükledim ve Postgres'ten tüm verileri taşıdım - aynı tablo yapısı, aynı indeksler, aynı veriler. İlk 100 afişi almak için aynı sorgu 0,3 saniyede çalışır :

SELECT TOP 100 u.id, u.username, COUNT(p.id) AS PostCount FROM dbo.users u
                    INNER JOIN dbo.posts p on p.user_id = u.id
                    WHERE u.username IS NOT NULL
                    GROUP BY u.id, u.username
ORDER BY PostCount DESC

Getirileri aynı aynı verilerden sonuçları, fakat 8 kat daha hızlı yapıyor. Ve Linux'ta MS SQL'in beta sürümü, sanırım onun "ev" işletim sistemi üzerinde çalışıyor - Windows Server - daha hızlı olabilir.

PostgreSQL sorgum tamamen yanlış mı yoksa PostgreSQL sadece yavaş mı?

ilave bilgi

Sürüm neredeyse en yeni (9.6.1, şu anda en yeni 9.6.2, ArchLinux sadece eski paketlere sahip ve güncellenmesi çok yavaş). Yapılandırma:

max_connections = 75
shared_buffers = 3584MB       
effective_cache_size = 10752MB
work_mem = 24466kB         
maintenance_work_mem = 896MB   
dynamic_shared_memory_type = posix  
min_wal_size = 1GB
max_wal_size = 2GB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100

EXPLAIN ANALYZEçıkışlar: https://pastebin.com/HxucRgnk

Denenmiş tüm dizinler, GIN ve GIST bile kullanılan, PostgreSQL için en hızlı yolu (ve Googling birçok satır ile onaylar) sıralı tarama kullanmaktır.

MS SQL Server 14.0.405.200-1, varsayılan konf.

Bunu bir API'de (analiz etmeden düz seçim ile) kullanıyorum ve bu API uç noktasını kromla çağırmak 2500 ms + sürdüğünü söylüyor, 50 ms HTTP ve web sunucusu ek yükü ekliyor (API ve SQL aynı sunucuda çalışıyor) - aynısı. Burada ya da orada 100 ms umurumda değil, umurumda iki tam saniye.

explain analyze SELECT user_id, count(9) FROM posts group by user_id;700 ms sürer. postsTablonun boyutu 2154 MB.


2
Göründüğü gibi, kullanıcılarınızdan güzel yağ mesajlarınız var (ortalama ~ 1kB). Bu şekilde postsbir tablo kullanarak bunları tablonun geri kalanından ayırmak mantıklı olabilir CREATE TABLE post_content (post_id PRIMARY KEY REFERENCES posts (id), content text);, bu tür sorgularda 'boşa harcanan' G / Ç'nin çoğu korunabilir. Mesajlar bu, bir daha küçük ise VACUUM FULLüzerinde poststeneke yardımı.
dezso

Evet, yayınlar, bir gönderinin tüm HTML koduna sahip içerik sütununa sahiptir. Öneriniz için teşekkür ederim, yarın deneyeceğim. Soru - MSSQL mesaj tablosu da 1.5 GB'nin üzerinde ve aynı içeriğe sahip, ancak oldukça hızlı olmayı başarıyor - neden?
Lars

2
SQL Server'dan da gerçek bir yürütme planı yayınlayabilirsiniz. Benim gibi insanları Postgres için bile gerçekten ilginç olabilir.
dezso

Hmm, hızlı guesss, bunu değiştirebilecek GROUP BY u.idbu GROUP BY p.user_idve bu deneyin? Benim tahminime göre, en iyi N - satırlarını almak için sadece user_id yazılarına ihtiyacınız olsa da, kullanıcı tablosu tanımlayıcısına göre gruplandırdığınız için Postgres birinci ve ikinci olarak grup katıyor.
UldisK

Yanıtlar:


1

Başka bir iyi sorgu varyantı:

SELECT p.user_id, p.cnt AS PostCount
FROM users u
INNER JOIN (
    select user_id, count(id) as cnt from posts group by user_id
) as p on p.user_id = u.id
WHERE u.username IS NOT NULL          
ORDER BY PostCount DESC LIMIT 100;

CTE'yi kullanmaz ve doğru cevap verir (ve CTE örneği teoride 100'den az satır üretebilir, çünkü ilk sınırlar daha sonra kullanıcılarla birleşir).

Herhalde, MSSQL'in sorgu iyileştiricisinde böyle bir dönüşüm gerçekleştirebildiğini ve PostgreSQL'in birleşim altında toplama işlemini aşağı çekemediğini düşünüyorum. Veya MSSQL sadece çok daha hızlı karma katılmak uygulaması var.


8

Bu işe yarayabilir veya çalışmayabilir - bunu grup ve filtreden önce tablolarınıza katıldığına dair bir bağırsak hissine dayandırıyorum. Aşağıdakileri denemenizi öneririz: filtre ve gruba katılmaya çalışmadan önce bir CTE kullanarak:

with
    __posts as(
        select
            user_id,
            count(1) as num_posts
        from
            posts
        group by
            user_id
        order by
            num_posts desc
        limit 100
    )
select
    users.username,
    __posts.num_posts
from
    users
    inner join __posts on(
        __posts.user_id = users.id
    )
order by
    num_posts desc

Sorgu planlayıcı bazen biraz yönlendirmeye ihtiyaç duyar. Bu çözüm burada iyi sonuç verir, ancak CTE'ler bazı durumlarda potansiyel olarak korkunç olabilir. CTE'ler yalnızca bellekte saklanır. Bunun sonucunda, büyük veri döndürmeleri Postgres'in ayrılan belleğini aşabilir ve değiştirmeye başlayabilir (MS'de sayfalama). CTE'ler de endekslenemez, bu nedenle yeterince büyük bir sorgu hala CTE'nizi sorgularken önemli ölçüde yavaşlamaya neden olabilir.

Gerçekten uzaklaştırabileceğiniz en iyi tavsiye, bunu birden çok şekilde denemek ve sorgu planlarınızı kontrol etmektir.


-1

Work_mem'i artırmaya çalıştınız mı? 24Mb çok küçük gibi görünüyor ve bu nedenle Hash Join birden çok toplu iş (geçici dosyalarda yazılıyor) kullanmak zorunda.


Çok küçük değil. 240 megabayta yükseltmek hiçbir şey yapmaz. Ne postgresql.conf yardımcı olacağını, bu iki satırı ekleyerek paralel sorguları sağlayan geçerli: max_parallel_workers_per_gather = 4vemax_worker_processes = 16
Lars
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.