Rất chậm đơn giản truy vấn THAM GIA


12

Cấu trúc DB đơn giản (cho một diễn đàn trực tuyến):

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

Khoảng 80k mục trong usersvà 2,6 triệu mục trong postsbảng. Truy vấn đơn giản này để có được 100 người dùng hàng đầu bởi bài đăng của họ mất 2,4 giây :

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

Với set enable_seqscan = falsethậm chí tệ hơn:

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

Nhóm theo usernamebị thiếu trong Postgres, vì không bắt buộc (SQL Server nói rằng tôi phải nhóm theo usernamenếu tôi muốn chọn tên người dùng). Nhóm với usernamethêm một chút ms vào thời gian thực hiện trên Postgres hoặc không làm gì cả.

Đối với khoa học, tôi đã cài đặt Microsoft SQL Server cho cùng một máy chủ (chạy archlinux, 8 lõi xeon, 24 gb ram, ssd) và di chuyển tất cả dữ liệu từ Postgres - cùng cấu trúc bảng, cùng chỉ mục, cùng dữ liệu. Truy vấn tương tự để có được 100 poster hàng đầu chạy trong 0,3 giây :

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

Mang lại kết quả tương tự từ cùng một dữ liệu, nhưng nó nhanh hơn 8 lần. Và đó là phiên bản beta của MS SQL trên Linux, tôi đoán chạy trên hệ điều hành "nhà" - Windows Server - nó có thể vẫn nhanh hơn.

Là truy vấn PostgreSQL của tôi hoàn toàn sai, hay PostgreSQL chỉ chậm?

thông tin bổ sung

Phiên bản gần như là phiên bản mới nhất (9.6.1, hiện tại mới nhất là 9.6.2, ArchLinux chỉ có các gói lỗi thời và rất chậm cập nhật). Cấu hình:

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đầu ra: https://pastebin.com/HxucRgnk

Đã thử tất cả các chỉ mục, được sử dụng ngay cả GIN và GIST, cách nhanh nhất để PostgreSQL (và Googling xác nhận với nhiều hàng) là sử dụng quét tuần tự.

MS SQL Server 14.0.405.200-1, mặc định conf.

Tôi sử dụng điều này trong một API (với lựa chọn đơn giản mà không phân tích) và gọi điểm cuối API này bằng chrome, nó cho biết phải mất 2500 ms + -, thêm 50 ms HTTP và chi phí trên máy chủ web (API và SQL chạy trên cùng một máy chủ) - Nó giống nhau. Tôi không quan tâm khoảng 100 ms ở đây hay ở đó, điều tôi quan tâm là hai giây.

explain analyze SELECT user_id, count(9) FROM posts group by user_id;mất 700 ms. Kích thước của postsbảng là 2154 MB.


2
Như âm thanh, bạn có bài viết chất béo tốt từ người dùng của bạn (trung bình ~ 1kB). Có thể có ý nghĩa khi tách chúng ra khỏi phần còn lại của postsbảng, bằng cách sử dụng bảng như CREATE TABLE post_content (post_id PRIMARY KEY REFERENCES posts (id), content text); vậy, hầu hết các I / O bị 'lãng phí' đối với loại truy vấn này có thể được tha. Nếu các bài viết nhỏ hơn thế này, một VACUUM FULLtrên postscó thể giúp đỡ.
dezso

Có, bài viết có cột nội dung có tất cả html của bài đăng. Cảm ơn lời đề nghị của bạn, sẽ thử vào ngày mai. Câu hỏi là - Bảng bài viết MSSQL cũng nặng hơn 1,5 GB và có cùng mục trong nội dung, nhưng quản lý khá nhanh hơn - tại sao?
Lars

2
Bạn cũng có thể đăng một kế hoạch thực hiện thực tế từ SQL Server. Có thể thực sự thú vị, ngay cả với những người Postgres như tôi.
dezso

Hmm, đoán nhanh, bạn có thể thay đổi điều GROUP BY u.idnày GROUP BY p.user_idvà thử nó không? Tôi đoán là, Postgres tham gia đầu tiên và nhóm theo thứ hai vì bạn đang nhóm theo định danh bảng người dùng, mặc dù bạn chỉ cần đăng user_id để có được N hàng hàng đầu.
UldisK

Câu trả lời:


1

Một biến thể truy vấn tốt khác là:

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;

Nó không khai thác CTE và đưa ra câu trả lời chính xác (và ví dụ về CTE có thể tạo ra ít hơn 100 hàng theo lý thuyết vì nó giới hạn đầu tiên sau đó tham gia với người dùng).

Tôi cho rằng, MSSQL có thể thực hiện chuyển đổi như vậy trong trình tối ưu hóa truy vấn của nó và PostgreQuery không thể đẩy tổng hợp dưới tham gia. Hoặc MSSQL chỉ cần thực hiện tham gia băm nhanh hơn nhiều.


8

Điều này có thể hoặc có thể không hoạt động - Tôi dựa trên cảm giác này rằng nó tham gia các bảng của bạn trước nhóm và bộ lọc. Tôi khuyên bạn nên thử các cách sau: lọc và nhóm bằng CTE trước khi thử tham gia:

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

Công cụ lập kế hoạch truy vấn đôi khi chỉ cần một chút hướng dẫn. Giải pháp này hoạt động tốt ở đây, nhưng CTE có thể có khả năng khủng khiếp trong một số trường hợp. CTE được lưu trữ độc quyền trong bộ nhớ. Do đó, lợi nhuận dữ liệu lớn có thể vượt quá bộ nhớ được phân bổ của Postgres và bắt đầu hoán đổi (phân trang trong MS). CTE cũng không thể được lập chỉ mục, do đó, một truy vấn đủ lớn vẫn có thể gây chậm đáng kể khi truy vấn CTE của bạn.

Lời khuyên tốt nhất bạn thực sự có thể bỏ đi là thử nhiều cách và kiểm tra kế hoạch truy vấn của bạn.


-1

Bạn đã cố gắng tăng work_mem? 24Mb dường như quá nhỏ và vì vậy Hash Join phải sử dụng nhiều lô (được ghi trong tệp tạm thời).


Nó không quá nhỏ. Tăng lên 240 megabyte không làm gì cả. Điều gì sẽ giúp ích trong postgresql.conf là cho phép truy vấn song song bằng cách thêm hai dòng này: max_parallel_workers_per_gather = 4max_worker_processes = 16
Lars
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.