Truy vấn chậm trên bảng lớn với NHÓM THEO VÀ ĐẶT HÀNG B BYNG


14

Tôi có một bảng với 7,2 triệu tuple trông như thế này:

                               table public.methods
 column |          type         |                      attributes
--------+-----------------------+----------------------------------------------------
 id     | integer               | not null DEFAULT nextval('methodkey'::regclass)
 hash   | character varying(32) | not null
 string | character varying     | not null
 method | character varying     | not null
 file   | character varying     | not null
 type   | character varying     | not null
Indexes:
    "methods_pkey" PRIMARY KEY, btree (id)
    "methodhash" btree (hash)

Bây giờ tôi muốn chọn một số giá trị nhưng truy vấn rất chậm:

db=# explain 
    select hash, string, count(method) 
    from methods 
    where hash not in 
          (select hash from nostring) 
    group by hash, string 
    order by count(method) desc;
                                            QUERY PLAN
----------------------------------------------------------------------------------------
 Sort  (cost=160245190041.10..160245190962.07 rows=368391 width=182)
   Sort Key: (count(methods.method))
   ->  GroupAggregate  (cost=160245017241.77..160245057764.73 rows=368391 width=182)
       ->  Sort  (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
             Sort Key: methods.hash, methods.string
             ->  Seq Scan on methods  (cost=0.00..160243305942.27 rows=3683905 width=182)
                   Filter: (NOT (SubPlan 1))
                   SubPlan 1
                   ->  Materialize  (cost=0.00..41071.54 rows=970636 width=33)
                     ->  Seq Scan on nostring  (cost=0.00..28634.36 rows=970636 width=33)

Các hashcột là băm md5 của stringvà có một chỉ mục. Vì vậy, tôi nghĩ rằng vấn đề của tôi là toàn bộ bảng được sắp xếp theo id chứ không phải theo hàm băm, vì vậy phải mất một thời gian để sắp xếp nó trước và sau đó nhóm lại?

Bảng nostringchỉ chứa một danh sách các giá trị băm mà tôi không muốn có. Nhưng tôi cần cả hai bảng để có tất cả các giá trị. Vì vậy, nó không phải là một lựa chọn để xóa chúng.

thông tin bổ sung: không có cột nào có thể là null (đã sửa trong định nghĩa bảng) và tôi đang sử dụng postgresql 9.2.


1
Luôn cung cấp phiên bản PostgreSQL bạn sử dụng. Tỷ lệ phần trăm của các NULLgiá trị trong cột là methodgì? Có trùng lặp trên string?
Erwin Brandstetter

Câu trả lời:


18

Các LEFT JOINtrong câu trả lời @ Dezső của nên được tốt. Tuy nhiên, một chỉ mục sẽ khó có ích (vì mỗi truy vấn) vì dù sao truy vấn phải đọc toàn bộ bảng - ngoại lệ là chỉ quét chỉ mục trong Postgres 9.2+ và các điều kiện thuận lợi, xem bên dưới.

SELECT m.hash, m.string, count(m.method) AS method_ct
FROM   methods m
LEFT   JOIN nostring n USING (hash)
WHERE  n.hash IS NULL
GROUP  BY m.hash, m.string 
ORDER  BY count(m.method) DESC;

Chạy EXPLAIN ANALYZEtrên truy vấn. Một vài lần để loại trừ hiệu ứng tiền mặt và tiếng ồn. So sánh kết quả tốt nhất.

Tạo một chỉ mục nhiều cột phù hợp với truy vấn của bạn:

CREATE INDEX methods_cluster_idx ON methods (hash, string, method);

Chờ đợi? Sau khi tôi nói một chỉ số sẽ không giúp đỡ? Vâng, chúng tôi cần nó để CLUSTERbàn:

CLUSTER methods USING methods_cluster_idx;
ANALYZE methods;

Chạy lại EXPLAIN ANALYZE. Còn nhanh hơn không? Nó nên

CLUSTERlà một hoạt động một lần để viết lại toàn bộ bảng theo thứ tự của chỉ mục được sử dụng. Nó cũng hiệu quả a VACUUM FULL. Nếu bạn muốn chắc chắn, bạn sẽ chạy thử nghiệm trước VACUUM FULLmột mình để xem những gì có thể được quy cho điều đó.

Nếu bảng của bạn nhìn thấy nhiều thao tác ghi, hiệu ứng sẽ giảm dần theo thời gian. Lịch trình CLUSTERvào giờ nghỉ để khôi phục hiệu quả. Tinh chỉnh phụ thuộc vào trường hợp sử dụng chính xác của bạn. Hướng dẫn về CLUSTER.

CLUSTERlà một công cụ khá thô, cần một khóa độc quyền trên bàn. Nếu bạn không đủ khả năng đó, hãy xem xét pg_repackcái nào có thể làm tương tự mà không có khóa độc quyền. Nhiều hơn trong câu trả lời sau này:


Nếu tỷ lệ phần trăm của các NULLgiá trị trong cột methodcao (hơn ~ 20 phần trăm, tùy thuộc vào kích thước hàng thực tế), một chỉ mục một phần sẽ giúp:

CREATE INDEX methods_foo_idx ON methods (hash, string)
WHERE method IS NOT NULL;

(Bản cập nhật sau này của bạn hiển thị các cột của bạn NOT NULL, vì vậy không thể áp dụng.)

Nếu bạn đang chạy PostgreSQL 9.2 trở lên (như @deszo đã nhận xét ), các chỉ mục được trình bày có thể hữu ích mà không có CLUSTERkế hoạch có thể sử dụng quét chỉ mục . Chỉ áp dụng trong các điều kiện thuận lợi: Không có thao tác ghi nào có thể ảnh hưởng đến bản đồ hiển thị kể từ lần cuối cùng VACUUMvà tất cả các cột trong truy vấn phải được bao phủ bởi chỉ mục. Về cơ bản các bảng chỉ đọc có thể sử dụng điều này bất cứ lúc nào, trong khi các bảng được viết nhiều bị hạn chế. Thêm chi tiết trong Wiki Postgres.

Chỉ số một phần được đề cập ở trên có thể thậm chí còn hữu ích hơn trong trường hợp đó.

Nếu , mặt khác, có không có NULL giá trị trong cột method, bạn nên
1.) định nghĩa nó NOT NULL
2.) sử dụng count(*)thay vì count(method), đó là nhanh hơn một chút và cũng làm như vậy trong sự vắng mặt của NULLcác giá trị.

Nếu bạn phải gọi truy vấn này thường xuyên và bảng chỉ đọc, hãy tạo một MATERIALIZED VIEW.


Điểm tốt kỳ lạ: Bảng của bạn được đặt tên nostring, nhưng dường như có chứa băm. Bằng cách loại trừ băm thay vì chuỗi, có thể bạn loại trừ nhiều chuỗi hơn dự định. Vô cùng khó, nhưng có thể.


với cụm của nó nhanh hơn nhiều. vẫn cần arround 5 phút cho truy vấn nhưng cách đó tốt hơn nhiều so với chạy suốt đêm: D
reox

@reox: Vì bạn chạy v9.2: Bạn đã kiểm tra chỉ với chỉ mục, trước khi phân cụm chưa? Sẽ rất thú vị nếu bạn thấy một sự khác biệt. (Bạn không thể tái tạo sự khác biệt sau khi phân cụm.) Ngoài ra (và điều này sẽ rẻ), EXPLAIN có hiển thị quét chỉ mục hoặc quét toàn bộ bảng bây giờ không?
Erwin Brandstetter

5

Chào mừng đến với DBA.SE!

Bạn có thể thử viết lại truy vấn của mình như thế này:

SELECT m.hash, string, count(method) 
FROM 
    methods m
    LEFT JOIN nostring n ON m.hash = n.hash
WHERE n.hash IS NULL
GROUP BY hash, string 
ORDER BY count(method) DESC;

hoặc khả năng khác:

SELECT m.hash, string, count(method) 
FROM 
    methods m
WHERE NOT EXISTS (SELECT hash FROM nostring WHERE hash = m.hash)
GROUP BY hash, string 
ORDER BY count(method) DESC;

NOT IN là một mức giảm điển hình cho hiệu suất vì khó có thể sử dụng một chỉ mục với nó.

Điều này có thể được tăng cường hơn nữa với các chỉ số. Một chỉ số trên có nostring.hashvẻ hữu ích. Nhưng trước tiên: bạn nhận được gì bây giờ? (Sẽ tốt hơn nếu thấy đầu ra EXPLAIN ANALYZEvì chi phí không cho biết thời gian hoạt động.)


một chỉ mục được tạo trên nostring.hash all yet, nhưng tôi nghĩ rằng postgres không sử dụng nó vì quá nhiều bộ dữ liệu ... khi tôi khám phá vô hiệu hóa trình tự quét, nó sử dụng chỉ mục. Nếu tôi sử dụng tham gia bên trái, tôi sẽ nhận được 32 triệu, vì vậy cách này tốt hơn ... nhưng tôi đang cố gắng tối ưu hóa nó nhiều hơn ...
reox

3
Chi phí chỉ dành cho người lập kế hoạch để có thể lập kế hoạch đủ tốt. Thời gian thực tế thường tương quan với nó, nhưng không nhất thiết phải như vậy. Vì vậy, nếu bạn muốn chắc chắn, sử dụng EXPLAIN ANALYZE.
dezso

1

Vì hàm băm là md5, nên bạn có thể cố gắng chuyển đổi nó thành một số: bạn có thể lưu nó dưới dạng số hoặc chỉ cần tạo một chỉ mục chức năng tính số đó trong một hàm bất biến.

Những người khác đã tạo ra một hàm pl / pssql để chuyển đổi (một phần) giá trị md5 từ văn bản sang chuỗi. Xem /programming/9809381/hashing-a-opes-to-a-numeric-value-in-postTHERql để biết ví dụ

Tôi tin rằng bạn thực sự đang dành nhiều thời gian để so sánh chuỗi trong khi quét chỉ mục. Nếu bạn quản lý để lưu trữ giá trị đó dưới dạng số, thì nó sẽ thực sự nhanh hơn.


1
Tôi nghi ngờ rằng việc chuyển đổi này sẽ tăng tốc mọi thứ. Tất cả các truy vấn ở đây sử dụng bình đẳng để so sánh. Tính toán các biểu diễn số và sau đó kiểm tra sự bằng nhau không hứa hẹn lợi nhuận lớn cho tôi.
dezso

2
Tôi nghĩ rằng tôi sẽ lưu trữ md5 dưới dạng bytea chứ không phải là một con số cho hiệu quả không gian: sqlfiddle.com/#!12/d41d8/252
Jack nói hãy thử topanswers.xyz

Ngoài ra, chào mừng bạn đến với dba.se!
Jack nói hãy thử topanswers.xyz

@JackDoumund: Nhận xét thú vị! 16 byte mỗi md5 thay vì 32 là một chút cho các bảng lớn.
Erwin Brandstetter

0

Tôi gặp vấn đề này rất nhiều, và phát hiện ra một mẹo 2 phần đơn giản.

  1. Tạo chỉ mục chuỗi con trên giá trị băm: (7 thường có độ dài tốt)

    create index methods_idx_hash_substring ON methods(substring(hash,1,7))

  2. Có các tìm kiếm / tham gia của bạn bao gồm một kết hợp chuỗi con, vì vậy trình hoạch định truy vấn được gợi ý để sử dụng chỉ mục:

    cũ: WHERE hash = :kwarg

    Mới: WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))

Bạn cũng nên có một chỉ số trên nguyên hash.

kết quả (thông thường) là trình hoạch định sẽ tham khảo chỉ số chuỗi con trước và loại bỏ hầu hết các hàng. sau đó nó khớp với hàm băm 32 ký tự đầy đủ với chỉ mục (hoặc bảng) tương ứng. Cách tiếp cận này đã giảm 800ms truy vấn xuống còn 4 đối với tôi.

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.