Cải thiện hiệu suất của COUNT / GROUP-BY trong bảng PostgresSQL lớn?


24

Tôi đang chạy PostgresSQL 9.2 và có quan hệ 12 cột với khoảng 6.700.000 hàng. Nó chứa các nút trong một không gian 3D, mỗi nút tham chiếu một người dùng (người đã tạo ra nó). Để truy vấn người dùng nào đã tạo bao nhiêu nút tôi làm như sau (được thêm vào explain analyzeđể biết thêm thông tin):

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

Như bạn có thể thấy, điều này mất khoảng 1,7 giây. Điều này không quá tệ khi xem xét lượng dữ liệu, nhưng tôi tự hỏi liệu điều này có thể được cải thiện. Tôi đã cố gắng thêm chỉ mục BTree trên cột người dùng, nhưng điều này không giúp được gì.

Bạn có đề xuất thay thế?


Để hoàn thiện, đây là định nghĩa bảng hoàn chỉnh với tất cả các chỉ mục của nó (không có ràng buộc, tham chiếu và kích hoạt khóa ngoài):

    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)

Chỉnh sửa: Đây là kết quả, khi tôi sử dụng truy vấn (và chỉ mục) được đề xuất bởi @ypercube (truy vấn mất khoảng 5,3 giây mà không có 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

Chỉnh sửa 2: Đây là kết quả, khi tôi sử dụng indexbật project_id, user_id(nhưng chưa tối ưu hóa lược đồ) như @ erwin-brandstetter đã đề xuất (truy vấn chạy với 1,5 giây với cùng tốc độ với truy vấn ban đầu của tôi):

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)

Bạn cũng có một bảng Usersvới user_idkhóa chính?
ypercubeᵀᴹ

Tôi chỉ thấy rằng có một addon của nhà cung cấp bên thứ ba cho Postgres. Ngoài ra, tôi chỉ muốn đăng bài từ ứng dụng ios mới
swasheck

2
Cảm ơn câu hỏi hay, rõ ràng, đầy đủ - phiên bản, định nghĩa bảng, v.v.
Craig Ringer

@ypercube Có, tôi đã có một bảng Người dùng.
tomka

Có bao nhiêu khác nhau project_iduser_id? Bảng có được cập nhật liên tục hay bạn có thể làm việc với chế độ xem cụ thể hóa (trong một thời gian)?
Erwin Brandstetter

Câu trả lời:


25

Vấn đề chính là chỉ số bị thiếu. Nhưng có nhiều hơn nữa.

SELECT user_id, count(*) AS ct
FROM   treenode
WHERE  project_id = 1
GROUP  BY user_id;
  • Bạn có nhiều bigintcột. Có lẽ là quá mức cần thiết. Thông thường, integerlà quá đủ cho các cột như project_iduser_id. Điều này cũng sẽ giúp các mục tiếp theo.
    Trong khi tối ưu hóa định nghĩa bảng, hãy xem xét câu trả lời liên quan này, nhấn mạnh vào việc căn chỉnhđệm dữ liệu . Nhưng hầu hết phần còn lại cũng áp dụng:

  • Con voi trong phòng : không có chỉ số trênproject_id . Tạo một cái. Điều này quan trọng hơn phần còn lại của câu trả lời này.
    Trong khi ở đó, làm cho nó một chỉ mục nhiều màu:

    CREATE INDEX treenode_project_id_user_id_index ON treenode (project_id, user_id);

    Nếu bạn làm theo lời khuyên của tôi, integersẽ hoàn hảo ở đây:

  • user_idđược định nghĩa NOT NULL, vì vậy count(user_id)tương đương với count(*), nhưng cái sau ngắn hơn và nhanh hơn một chút. (Trong truy vấn cụ thể này, điều này thậm chí sẽ áp dụng mà không user_idđược xác định NOT NULL.)

  • idđã là khóa chính, UNIQUEràng buộc bổ sung là chấn lưu vô dụng . Thả nó:

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

    Ngoài ra: Tôi không sử dụng idlàm tên cột. Sử dụng một cái gì đó mô tả như thế nào treenode_id.

Đã thêm thông tin

Hỏi: How many different project_id and user_id?
A : not more than five different project_id.

Điều đó có nghĩa là Postgres phải đọc khoảng 20% ​​toàn bộ bảng để đáp ứng truy vấn của bạn. Trừ khi nó có thể sử dụng quét chỉ mục , quét tuần tự trên bảng sẽ nhanh hơn so với bất kỳ chỉ mục nào. Không có thêm hiệu suất để đạt được ở đây - ngoại trừ bằng cách tối ưu hóa cài đặt bảng và máy chủ.

Đối với quét chỉ mục : Để xem mức độ hiệu quả có thể, hãy chạy VACUUM ANALYZEnếu bạn có đủ khả năng đó (chỉ khóa bảng). Sau đó thử lại truy vấn của bạn. Bây giờ nó sẽ nhanh hơn vừa phải chỉ sử dụng chỉ mục. Đọc câu trả lời liên quan này trước:

Cũng như trang hướng dẫn được thêm bằng Postgres 9.6Wiki Postgres khi quét chỉ mục .


1
Erwin, cảm ơn lời đề nghị của bạn. Bạn đúng, cho user_idproject_id integernên là quá đủ. Sử dụng count(*)thay vì count(user_id)tiết kiệm khoảng 70ms ở đây, đó là điều tốt để biết. Tôi đã thêm EXPLAIN ANALYZEtruy vấn sau khi tôi đã thêm đề xuất của bạn indexvào bài đăng đầu tiên. Nó không cải thiện hiệu suất, mặc dù (nhưng cũng không bị tổn thương). Có vẻ như indexkhông được sử dụng ở tất cả. Tôi sẽ kiểm tra tối ưu hóa lược đồ sớm.
tomka

1
Nếu tôi tắt seqscan, chỉ mục được sử dụng ( Index Only Scan using treenode_project_id_user_id_index on treenode), nhưng truy vấn mất khoảng 2,5 giây sau đó (dài hơn khoảng 1 giây so với seqscan).
tomka

1
Cảm ơn sự cập nhập của bạn. Những bit bị thiếu nên là một phần của câu hỏi của tôi, điều đó là đúng. Tôi chỉ không nhận thức được tác động của họ. Tôi sẽ tối ưu hóa lược đồ của mình như bạn đề xuất --- hãy xem tôi có thể đạt được gì từ đó. Cảm ơn lời giải thích của bạn, nó có ý nghĩa với tôi và do đó tôi sẽ đánh dấu câu trả lời của bạn là câu trả lời được chấp nhận.
tomka

7

Trước tiên tôi muốn thêm một chỉ mục (project_id, user_id)và sau đó trong phiên bản 9.3, hãy thử truy vấn này:

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

Trong 9.2, hãy thử cái này:

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 ;

Tôi giả sử bạn có một cái usersbàn. Nếu không, thay thế usersbằng:
(SELECT DISTINCT user_id FROM treenode)


Cảm ơn bạn rất nhiều vì câu trả lời của bạn. Bạn đã đúng, tôi đã có một bảng người dùng. Tuy nhiên, sử dụng truy vấn của bạn trong 9.2, sẽ mất khoảng 5 giây để có kết quả - bất kể chỉ mục có được tạo hay không. Tôi đã tạo chỉ mục như thế này : CREATE INDEX treenode_user_index ON treenode USING btree (project_id, user_id);, nhưng tôi cũng đã thử mà không có USINGmệnh đề. Tôi có bỏ lỡ điều gì không?
tomka

Có bao nhiêu hàng trong usersbảng và truy vấn trả về bao nhiêu hàng (vậy có bao nhiêu người dùng ở đó project_id=1)? Bạn có thể hiển thị giải thích của truy vấn này, sau khi bạn thêm chỉ mục?
ypercubeᵀᴹ

1
Đầu tiên, tôi đã sai trong bình luận đầu tiên của tôi. Nếu không có chỉ số được đề xuất của bạn, sẽ mất khoảng 40 giây (!) Để truy xuất kết quả. Phải mất khoảng 5s với indexvị trí. Xin lỗi vì sự nhầm lẫn. Trong usersbảng của tôi, tôi đã có 46 mục. Truy vấn chỉ trả về 9 hàng. Đáng ngạc nhiên, SELECT DISTINCT user_id FROM treenode WHERE project_id=1;trả lại 38 hàng. Tôi đã thêm explainbài viết đầu tiên của mình. Và để tránh nhầm lẫn: usersbảng của tôi thực sự được gọi auth_user.
tomka

Tôi tự hỏi làm thế nào có thể SELECT DISTINCT user_id FROM treenode WHERE project_id=1;trả về 38 hàng trong khi các truy vấn chỉ trả về 9. Được đệm.
ypercubeᵀᴹ

Bạn có thể thử cái này không ?:SET enable_seqscan = OFF; (Query); SET enable_seqscan = ON;
ypercubeᵀᴹ
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.