Postgres đang thực hiện quét tuần tự thay vì quét chỉ mục


9

Tôi có một bảng có khoảng 10 triệu hàng trong đó và một chỉ mục trên trường ngày. Khi tôi thử và trích xuất các giá trị duy nhất của trường được lập chỉ mục, Postgres chạy quét tuần tự mặc dù tập kết quả chỉ có 26 mục. Tại sao tối ưu hóa chọn kế hoạch này? Và tôi có thể làm gì để tránh nó?

Từ các câu trả lời khác, tôi nghi ngờ điều này có liên quan nhiều đến truy vấn cũng như chỉ mục.

explain select "labelDate" from pages group by "labelDate";
                              QUERY PLAN
-----------------------------------------------------------------------
 HashAggregate  (cost=524616.78..524617.04 rows=26 width=4)
   Group Key: "labelDate"
   ->  Seq Scan on pages  (cost=0.00..499082.42 rows=10213742 width=4)
(3 rows)

Cấu trúc bảng:

http=# \d pages
                                       Table "public.pages"
     Column      |          Type          |        Modifiers
-----------------+------------------------+----------------------------------
 pageid          | integer                | not null default nextval('...
 createDate      | integer                | not null
 archive         | character varying(16)  | not null
 label           | character varying(32)  | not null
 wptid           | character varying(64)  | not null
 wptrun          | integer                | not null
 url             | text                   |
 urlShort        | character varying(255) |
 startedDateTime | integer                |
 renderStart     | integer                |
 onContentLoaded | integer                |
 onLoad          | integer                |
 PageSpeed       | integer                |
 rank            | integer                |
 reqTotal        | integer                | not null
 reqHTML         | integer                | not null
 reqJS           | integer                | not null
 reqCSS          | integer                | not null
 reqImg          | integer                | not null
 reqFlash        | integer                | not null
 reqJSON         | integer                | not null
 reqOther        | integer                | not null
 bytesTotal      | integer                | not null
 bytesHTML       | integer                | not null
 bytesJS         | integer                | not null
 bytesCSS        | integer                | not null
 bytesHTML       | integer                | not null
 bytesJS         | integer                | not null
 bytesCSS        | integer                | not null
 bytesImg        | integer                | not null
 bytesFlash      | integer                | not null
 bytesJSON       | integer                | not null
 bytesOther      | integer                | not null
 numDomains      | integer                | not null
 labelDate       | date                   |
 TTFB            | integer                |
 reqGIF          | smallint               | not null
 reqJPG          | smallint               | not null
 reqPNG          | smallint               | not null
 reqFont         | smallint               | not null
 bytesGIF        | integer                | not null
 bytesJPG        | integer                | not null
 bytesPNG        | integer                | not null
 bytesFont       | integer                | not null
 maxageMore      | smallint               | not null
 maxage365       | smallint               | not null
 maxage30        | smallint               | not null
 maxage1         | smallint               | not null
 maxage0         | smallint               | not null
 maxageNull      | smallint               | not null
 numDomElements  | integer                | not null
 numCompressed   | smallint               | not null
 numHTTPS        | smallint               | not null
 numGlibs        | smallint               | not null
 numErrors       | smallint               | not null
 numRedirects    | smallint               | not null
 maxDomainReqs   | smallint               | not null
 bytesHTMLDoc    | integer                | not null
 maxage365       | smallint               | not null
 maxage30        | smallint               | not null
 maxage1         | smallint               | not null
 maxage0         | smallint               | not null
 maxageNull      | smallint               | not null
 numDomElements  | integer                | not null
 numCompressed   | smallint               | not null
 numHTTPS        | smallint               | not null
 numGlibs        | smallint               | not null
 numErrors       | smallint               | not null
 numRedirects    | smallint               | not null
 maxDomainReqs   | smallint               | not null
 bytesHTMLDoc    | integer                | not null
 fullyLoaded     | integer                |
 cdn             | character varying(64)  |
 SpeedIndex      | integer                |
 visualComplete  | integer                |
 gzipTotal       | integer                | not null
 gzipSavings     | integer                | not null
 siteid          | numeric                |
Indexes:
    "pages_pkey" PRIMARY KEY, btree (pageid)
    "pages_date_url" UNIQUE CONSTRAINT, btree ("urlShort", "labelDate")
    "idx_pages_cdn" btree (cdn)
    "idx_pages_labeldate" btree ("labelDate") CLUSTER
    "idx_pages_urlshort" btree ("urlShort")
Triggers:
    pages_label_date BEFORE INSERT OR UPDATE ON pages
      FOR EACH ROW EXECUTE PROCEDURE fix_label_date()

Câu trả lời:


8

Đây là một vấn đề được biết đến liên quan đến tối ưu hóa Postgres. Nếu các giá trị riêng biệt là rất ít - như trong trường hợp của bạn - và bạn đang ở phiên bản 8.4+, một cách giải quyết rất nhanh bằng cách sử dụng truy vấn đệ quy được mô tả ở đây: Loose Indexscan .

Truy vấn của bạn có thể được viết lại ( LATERALphiên bản nhu cầu 9.3+):

WITH RECURSIVE pa AS 
( ( SELECT labelDate FROM pages ORDER BY labelDate LIMIT 1 ) 
  UNION ALL
    SELECT n.labelDate 
    FROM pa AS p
         , LATERAL 
              ( SELECT labelDate 
                FROM pages 
                WHERE labelDate > p.labelDate 
                ORDER BY labelDate 
                LIMIT 1
              ) AS n
) 
SELECT labelDate 
FROM pa ;

Erwin Brandstetter có một lời giải thích kỹ lưỡng và một số biến thể của truy vấn trong câu trả lời này (về một vấn đề có liên quan nhưng khác nhau): Tối ưu hóa truy vấn GROUP BY để truy xuất bản ghi mới nhất cho mỗi người dùng


6

Các truy vấn tốt nhất rất nhiều phụ thuộc vào phân phối dữ liệu .

Bạn có nhiều hàng mỗi ngày, đã được thiết lập. Vì trường hợp của bạn chỉ giảm 26 giá trị trong kết quả, nên tất cả các giải pháp sau đây sẽ nhanh chóng được sử dụng ngay khi chỉ mục được sử dụng.
(Đối với các giá trị khác biệt hơn, trường hợp sẽ trở nên thú vị hơn.)

Không cần phải liên quan pageid gì cả (như bạn đã nhận xét).

Mục lục

Tất cả bạn cần là một chỉ số btree đơn giản trên "labelDate".
Với nhiều hơn một vài giá trị NULL trong cột, một chỉ mục một phần giúp nhiều hơn (và nhỏ hơn):

CREATE INDEX pages_labeldate_nonull_idx ON big ("labelDate")
WHERE  "labelDate" IS NOT NULL;

Sau này bạn đã làm rõ:

0% NULL nhưng chỉ sau khi sửa chữa mọi thứ khi nhập.

Chỉ mục một phần vẫn có thể có ý nghĩa để loại trừ trạng thái trung gian của các hàng có giá trị NULL. Sẽ tránh các cập nhật không cần thiết cho chỉ mục (với kết quả phình to).

Truy vấn

Dựa trên một phạm vi tạm thời

Nếu ngày của bạn xuất hiện trong một phạm vi liên tục với không quá nhiều khoảng trống , chúng tôi có thể sử dụng tính chất của loại dữ liệu dateđể lợi thế của chúng tôi. Chỉ có một số lượng hữu hạn, có thể đếm được các giá trị giữa hai giá trị đã cho. Nếu khoảng trống là ít, điều này sẽ nhanh nhất:

SELECT d."labelDate"
FROM  (
   SELECT generate_series(min("labelDate")::timestamp
                        , max("labelDate")::timestamp
                        , interval '1 day')::date AS "labelDate"
   FROM   pages
   ) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

Tại sao dàn diễn viên timestampvào generate_series()? Xem:

Tối thiểu và tối đa có thể được chọn từ chỉ số với giá rẻ. Nếu bạn biết ngày tối thiểu và / hoặc tối đa có thể, nó sẽ rẻ hơn một chút. Thí dụ:

SELECT d."labelDate"
FROM  (SELECT date '2011-01-01' + g AS "labelDate"
       FROM   generate_series(0, now()::date - date '2011-01-01' - 1) g) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

Hoặc, trong một khoảng thời gian không thay đổi:

SELECT d."labelDate"
FROM  (SELECT date '2011-01-01' + g AS "labelDate"
       FROM generate_series(0, 363) g) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

Quét chỉ mục lỏng lẻo

Điều này thực hiện rất tốt với bất kỳ phân phối ngày nào (miễn là chúng tôi có nhiều hàng mỗi ngày). Về cơ bản những gì @ypercube đã cung cấp . Nhưng có một số điểm tốt và chúng tôi cần đảm bảo chỉ số yêu thích của chúng tôi có thể được sử dụng ở mọi nơi.

WITH RECURSIVE p AS (
   ( -- parentheses required for LIMIT
   SELECT "labelDate"
   FROM   pages
   WHERE  "labelDate" IS NOT NULL
   ORDER  BY "labelDate"
   LIMIT  1
   ) 
   UNION ALL
   SELECT (SELECT "labelDate" 
           FROM   pages 
           WHERE  "labelDate" > p."labelDate" 
           ORDER  BY "labelDate" 
           LIMIT  1)
   FROM   p
   WHERE  "labelDate" IS NOT NULL
   ) 
SELECT "labelDate" 
FROM   p
WHERE  "labelDate" IS NOT NULL;
  • CTE đầu tiên pcó hiệu quả tương tự như

    SELECT min("labelDate") FROM pages

    Nhưng hình thức dài dòng đảm bảo chỉ số một phần của chúng tôi được sử dụng. Thêm vào đó, hình thức này thường nhanh hơn một chút trong trải nghiệm của tôi (và trong các thử nghiệm của tôi).

  • Đối với chỉ một cột duy nhất, các truy vấn con tương quan trong thuật ngữ đệ quy của rCTE phải nhanh hơn một chút. Điều này yêu cầu loại trừ các hàng dẫn đến NULL cho "nhãnDate". Xem:

  • Tối ưu hóa truy vấn GROUP BY để truy xuất bản ghi mới nhất cho mỗi người dùng

Ngoài ra

Không xác định, hợp pháp, định danh trường hợp thấp hơn làm cho cuộc sống của bạn dễ dàng hơn.
Sắp xếp các cột trong định nghĩa bảng của bạn một cách thuận lợi để tiết kiệm một số dung lượng đĩa:


-2

Từ tài liệu postgresql:

CLUSTER có thể sắp xếp lại bảng bằng cách quét chỉ mục trên chỉ mục đã chỉ định hoặc (nếu chỉ mục là cây b), quét tuần tự theo sau là sắp xếp . Nó sẽ cố gắng chọn phương pháp sẽ nhanh hơn, dựa trên các tham số chi phí kế hoạch và thông tin thống kê có sẵn.

Chỉ mục của bạn trên nhãnDate là btree ..

Tài liệu tham khảo:

http://www.postgresql.org/docs/9.1/static/sql-cluster.html


Ngay cả với một điều kiện như 'WHERE "nhãnDate" GIỮA' 2000-01-01 'và' 2020-01-01 'vẫn liên quan đến việc quét tuần tự.
Charlie Clark

Phân cụm tại thời điểm này (mặc dù dữ liệu đã được nhập vào theo thứ tự đó). Điều đó vẫn không thực sự giải thích quyết định của trình hoạch định truy vấn không sử dụng chỉ mục ngay cả với mệnh đề WHERE.
Charlie Clark

Bạn đã cố gắng để vô hiệu hóa quét tuần tự cho phiên? set enable_seqscan=offIN bất kỳ trường hợp tài liệu là rõ ràng. Nếu bạn gom nó sẽ thực hiện quét tuần tự.
Fabrizio Mazzoni

Có, tôi đã thử vô hiệu hóa quét tuần tự nhưng nó không tạo ra nhiều khác biệt. Tốc độ của truy vấn này không thực sự quan trọng khi tôi sử dụng nó để tạo bảng tra cứu mà sau đó có thể được sử dụng cho THAM GIA trong các truy vấn thực.
Charlie Clark
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.