Postgres melakukan pemindaian berurutan alih-alih pemindaian indeks

9

Saya memiliki tabel dengan sekitar 10 juta baris di dalamnya dan indeks pada bidang tanggal. Ketika saya mencoba dan mengekstrak nilai unik dari bidang yang diindeks Postgres menjalankan pemindaian berurutan meskipun set hasil hanya memiliki 26 item. Mengapa pengoptimal memilih rencana ini? Dan apa yang bisa saya lakukan untuk menghindarinya?

Dari jawaban lain, saya kira ini terkait dengan permintaan dan indeks.

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)

Struktur meja:

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()
Charlie Clark
sumber

Jawaban:

8

Ini adalah masalah yang diketahui mengenai optimasi Postgres. Jika nilai yang berbeda sedikit - seperti dalam kasus Anda - dan Anda berada di versi 8.4+, solusi yang sangat cepat menggunakan kueri rekursif dijelaskan di sini: Loose Indexscan .

Permintaan Anda dapat ditulis ulang (versi LATERALkebutuhan 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 memiliki penjelasan menyeluruh dan beberapa variasi kueri dalam jawaban ini (pada masalah terkait tetapi berbeda): Optimalkan GROUP BY kueri untuk mengambil catatan terbaru per pengguna

ypercubeᵀᴹ
sumber
6

Permintaan terbaik sangat tergantung pada distribusi data .

Anda memiliki banyak baris per tanggal, yang telah ditetapkan. Karena kasing Anda terbakar hingga hanya 26 nilai dalam hasilnya, semua solusi berikut akan sangat cepat segera setelah indeks digunakan.
(Untuk nilai yang lebih berbeda, case akan menjadi lebih menarik.)

Tidak perlu melibatkan pageid sama sekali (seperti Anda berkomentar).

Indeks

Yang Anda butuhkan hanyalah indeks btree sederhana "labelDate".
Dengan lebih dari beberapa nilai NULL dalam kolom, indeks parsial membantu beberapa lebih (dan lebih kecil):

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

Anda kemudian mengklarifikasi:

0% NULL tetapi hanya setelah memperbaiki ketika mengimpor.

Indeks parsial mungkin masih masuk akal untuk mengesampingkan status perantara baris dengan nilai NULL. Akan menghindari pembaruan indeks yang tidak perlu (dengan hasil mengasapi).

Pertanyaan

Berdasarkan rentang sementara

Jika tanggal Anda muncul dalam rentang berkelanjutan dengan tidak terlalu banyak celah , kami dapat menggunakan sifat tipe data dateuntuk keuntungan kami. Hanya ada sejumlah nilai yang terbatas dan dapat dihitung antara dua nilai yang diberikan. Jika celahnya sedikit, ini akan menjadi yang tercepat:

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

Mengapa cor untuk timestampdi generate_series()? Lihat:

Min dan maks dapat diambil dari indeks dengan murah. Jika Anda tahu tanggal minimum dan / atau maksimum yang mungkin, itu masih sedikit lebih murah. Contoh:

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

Atau, untuk interval yang tidak berubah:

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

Pemindaian indeks longgar

Ini berkinerja sangat baik dengan distribusi tanggal apa pun (selama kami memiliki banyak baris per tanggal). Pada dasarnya apa yang sudah disediakan @ypercube . Tetapi ada beberapa poin bagus dan kita perlu memastikan indeks favorit kita dapat digunakan di mana-mana.

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 pertama psecara efektif sama dengan

    SELECT min("labelDate") FROM pages

    Tetapi bentuk verbose memastikan indeks parsial kami digunakan. Plus, formulir ini biasanya sedikit lebih cepat dalam pengalaman saya (dan dalam tes saya).

  • Untuk hanya satu kolom, subqueries yang dikorelasikan dalam istilah rekursif dari rCTE harus sedikit lebih cepat. Ini mengharuskan untuk mengecualikan baris yang menghasilkan NULL untuk "labelDate". Lihat:

  • Optimalkan GROUP BY query untuk mengambil catatan terbaru per pengguna

Selain itu

Pengidentifikasi huruf kecil tanpa tanda kutip, legal, membuat hidup Anda lebih mudah.
Pesan kolom dalam definisi tabel Anda secara menguntungkan untuk menghemat ruang disk:

Erwin Brandstetter
sumber
-2

Dari dokumentasi postgresql:

CLUSTER dapat mengurutkan kembali tabel menggunakan pemindaian indeks pada indeks yang ditentukan, atau (jika indeks adalah b-tree) pemindaian berurutan diikuti dengan pengurutan . Ini akan berusaha untuk memilih metode yang akan lebih cepat, berdasarkan parameter biaya perencana dan informasi statistik yang tersedia.

Indeks Anda pada labelDate adalah btree ..

Referensi:

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

Fabrizio Mazzoni
sumber
Bahkan dengan kondisi seperti `WHERE" labelDate "BETWEEN '2000-01-01' dan '2020-01-01' masih melibatkan pemindaian berurutan.
Charlie Clark
Clustering saat ini (meskipun data dimasukkan secara kasar dalam urutan itu). Itu masih tidak benar-benar menjelaskan keputusan perencana permintaan untuk tidak menggunakan indeks bahkan dengan klausa WHERE.
Charlie Clark
Sudahkah Anda mencoba juga menonaktifkan pemindaian berurutan untuk sesi ini? set enable_seqscan=offBagaimanapun dokumentasi jelas. Jika Anda mengelompokkannya akan melakukan pemindaian berurutan.
Fabrizio Mazzoni
Ya, saya mencoba menonaktifkan pemindaian berurutan tetapi tidak membuat banyak perbedaan. Kecepatan kueri ini sebenarnya tidak penting karena saya menggunakannya untuk membuat tabel pencarian yang kemudian dapat digunakan untuk BERGABUNG dalam permintaan nyata.
Charlie Clark