cara menggunakan indeks untuk mempercepat penyortiran postgres

10

Saya menggunakan postgres 9.4.

The messagesmemiliki skema berikut: pesan milik FEED_ID, dan memiliki posted_at, juga pesan dapat memiliki pesan orang tua (dalam kasus balasan).

                    Table "public.messages"
            Column            |            Type             | Modifiers
------------------------------+-----------------------------+-----------
 message_id                   | character varying(255)      | not null
 feed_id                      | integer                     |
 parent_id                    | character varying(255)      |
 posted_at                    | timestamp without time zone |
 share_count                  | integer                     |
Indexes:
    "messages_pkey" PRIMARY KEY, btree (message_id)
    "index_messages_on_feed_id_posted_at" btree (feed_id, posted_at DESC NULLS LAST)

Saya ingin mengembalikan semua pesan yang dipesan share_count, tetapi untuk masing-masing parent_id, saya hanya ingin mengembalikan satu pesan. yaitu, jika beberapa pesan memiliki yang sama parent_id, maka hanya yang terbaru ( posted_at) yang dikembalikan. The parent_iddapat null, pesan dengan nol parent_idsemua harus kembali.

Permintaan yang saya gunakan adalah:

WITH filtered_messages AS (SELECT * 
                           FROM messages
                           WHERE feed_id IN (7) 
                           AND (posted_at >= '2015-01-01 04:00:00.000000') 
                           AND (posted_at < '2015-04-28 04:00:00.000000'))
    SELECT *
    FROM (SELECT DISTINCT ON(COALESCE(parent_id, message_id)) parent_id,
                          message_id, 
                          posted_at, 
                          share_count
          FROM filtered_messages
          ORDER BY COALESCE(parent_id, message_id), posted_at DESC NULLS LAST
         ) messages
    ORDER BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;

Inilah http://sqlfiddle.com/#!15/588e5/1/0 , dalam SQL Fiddle, saya telah mendefinisikan skema, kueri yang tepat, dan hasil yang diharapkan.

Tetapi kinerja permintaan lambat begitu tabel pesan menjadi besar. Saya mencoba menambahkan beberapa indeks penyortiran, tetapi sepertinya tidak menggunakan indeks. Inilah penjelasannya: http://explain.depesz.com/s/Sv2

Bagaimana saya bisa membuat indeks yang benar?

Zhaohan Weng
sumber
Pada pandangan pertama, ORDER BYdalam subquery sama sekali tidak berguna. Lebih lanjut, rencana tertaut tidak dapat merupakan hasil dari permintaan yang diposting - tidak ada penyebutan metadata, misalnya.
dezso
Deskripsi Anda tidak mencakup peran feed_iddan posted_atdan Anda tidak menyebutkan metadatasama sekali, yang tampaknya merupakan tipe JSON? Harap perbaiki pertanyaan Anda agar konsisten. Anda pilih> 500k baris dalam CTE ... Berapa banyak baris dalam tabel? Berapa persentase baris yang biasanya Anda pilih dalam CTE? Berapa persentase baris yang dimiliki parent_id IS NULL? Pertimbangkan info dalam tag [kinerja-postgresql] untuk pertanyaan kinerja.
Erwin Brandstetter
Juga penting: Berapa banyak baris untuk masing-masing parent_id? (minimal / rata-rata)
Erwin Brandstetter
maaf, saya mencoba membuat pertanyaan lebih jelas dengan mengurangi beberapa kolom, share_count sebenarnya ada di hstore metadata. Saat ini tabel pesan memiliki 10 juta data, tetapi bertambah cepat. Saya pikir untuk memisahkan ke dalam tabel partisi untuk setiap feed_id. Karena saya hanya mengambil per id umpan. persentase parent_id null vs bukan null adalah sekitar 60% / 40%. pengambilan tipikal adalah sekitar 1-2% dari tabel. (sekitar 100K pesan) Kinerja untuk 100K adalah sekitar 1s, tetapi sekali sampai 500K + itu menggunakan indeks bitmap dan biasanya membutuhkan 10s.
Zhaohan Weng

Jawaban:

9

Pertanyaan

Pertanyaan ini seharusnya jauh lebih cepat dalam hal apa pun:

SELECT parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NULL  -- match index condition
UNION ALL
(
SELECT DISTINCT ON(parent_id)
       parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NOT NULL  -- match index condition
ORDER  BY parent_id, posted_at DESC NULLS LAST
)
ORDER  BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;
  • CTE tidak melakukan apa pun di sini yang tidak dapat disampaikan oleh subquery biasa. Dan CTE memperkenalkan penghalang optimasi karena dijalankan secara terpisah dan hasilnya terwujud.

  • Anda memiliki satu tingkat subquery lebih dari yang sebenarnya Anda butuhkan.

  • Ekspresi (COALESCE(parent_id, message_id)tidak kompatibel dengan indeks biasa, Anda perlu indeks pada ekspresi itu. Tetapi itu mungkin juga tidak terlalu berguna, tergantung pada distribusi data. Ikuti tautan saya di bawah ini untuk informasi terperinci.

  • Membagi kasus sederhana parent_id IS NULLmenjadi terpisah SELECTmungkin atau tidak memberikan yang optimal. Terutama tidak, jika itu kasus yang jarang terjadi, dalam hal ini permintaan gabungan dengan indeks aktif (COALESCE(parent_id, message_id)dapat bekerja lebih baik. Pertimbangan lain berlaku ...

Indeks

Terutama ketika didukung dengan indeks ini:

CREATE INDEX messages_idx_null ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NULL;

CREATE INDEX messages_idx_notnull ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NOT NULL;

Dua indeks parsial mencakup seluruh tabel bersama - sama dan hampir sama ukurannya sebagai satu indeks total.

Dua kolom terakhir parent_id, message_idhanya masuk akal jika Anda hanya mendapatkan scan indeks saja . Hapus mereka dari kedua indeks.

SQL Fiddle.

Bergantung pada detail yang hilang, DISTINCT ONmungkin atau mungkin bukan teknik permintaan terbaik untuk tujuan tersebut. Dapatkan penjelasan rinci di sini:

Dan mungkin alternatif yang lebih cepat di sini:

Erwin Brandstetter
sumber