PostgreSQL
 sql >> Teknologi Basis Data >  >> RDS >> PostgreSQL

Mengapa sedikit perubahan dalam istilah penelusuran sangat memperlambat kueri?

Mengapa?

Alasan apakah ini:

Permintaan cepat:

->  Hash Left Join  (cost=1378.60..2467.48 rows=15 width=79) (actual time=41.759..85.037 rows=1129 loops=1)
      ...
      Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* (...)

Permintaan lambat:

->  Hash Left Join  (cost=1378.60..2467.48 rows=1 width=79) (actual time=35.084..80.209 rows=1129 loops=1)
      ...
      Filter: (unaccent(((((COALESCE(p.abrev, ''::character varying))::text || ' ('::text) || (COALESCE(p.prenome, ''::character varying))::text) || ')'::text)) ~~* unacc (...)

Memperluas pola pencarian dengan karakter lain menyebabkan Postgres mengasumsikan lebih sedikit hit. (Biasanya, ini adalah perkiraan yang masuk akal.) Postgres jelas tidak memiliki statistik yang cukup tepat (tidak ada, sebenarnya, terus membaca) untuk mengharapkan jumlah klik yang sama dengan yang Anda dapatkan.

Hal ini menyebabkan peralihan ke paket kueri yang berbeda, yang bahkan kurang optimal untuk aktual jumlah klik rows=1129 .

Solusi

Dengan asumsi Postgres 9.5 saat ini karena belum dideklarasikan.

Salah satu cara untuk memperbaiki situasi adalah dengan membuat indeks ekspresi pada ekspresi dalam predikat. Ini membuat Postgres mengumpulkan statistik untuk ekspresi aktual, yang dapat membantu kueri bahkan jika indeks itu sendiri tidak digunakan untuk kueri . Tanpa indeks, tidak ada statistik untuk ekspresi sama sekali. Dan jika dilakukan dengan benar, indeks dapat digunakan untuk kueri, itu bahkan jauh lebih baik. Tapi ada banyak masalah dengan ekspresi Anda saat ini:

unaccent(TEXT(coalesce(p.abrev,'')||' ('||coalesce(p.prenome,'')||')')) ilike unaccent('%vicen%')

Pertimbangkan kueri yang diperbarui ini, berdasarkan beberapa asumsi tentang definisi tabel rahasia Anda:

SELECT e.id
     , (SELECT count(*) FROM imgitem
        WHERE tabid = e.id AND tab = 'esp') AS imgs -- count(*) is faster
     , e.ano, e.mes, e.dia
     , e.ano::text || to_char(e.mes2, 'FM"-"00')
                   || to_char(e.dia,  'FM"-"00') AS data    
     , pl.pltag, e.inpa, e.det, d.ano anodet
     , format('%s (%s)', p.abrev, p.prenome) AS determinador
     , d.tax
     , coalesce(v.val,v.valf)   || ' ' || vu.unit  AS altura
     , coalesce(v1.val,v1.valf) || ' ' || vu1.unit AS dap
     , d.fam, tf.nome família, d.gen, tg.nome AS gênero, d.sp
     , ts.nome AS espécie, d.inf, e.loc, l.nome localidade, e.lat, e.lon
FROM      pess    p                        -- reorder!
JOIN      det     d   ON d.detby   = p.id  -- INNER JOIN !
LEFT JOIN tax     tf  ON tf.oldfam = d.fam
LEFT JOIN tax     tg  ON tg.oldgen = d.gen
LEFT JOIN tax     ts  ON ts.oldsp  = d.sp
LEFT JOIN tax     ti  ON ti.oldinf = d.inf  -- unused, see @joop's comment
LEFT JOIN esp     e   ON e.det     = d.id
LEFT JOIN loc     l   ON l.id      = e.loc
LEFT JOIN var     v   ON v.esp     = e.id AND v.key  = 265
LEFT JOIN varunit vu  ON vu.id     = v.unit
LEFT JOIN var     v1  ON v1.esp    = e.id AND v1.key = 264
LEFT JOIN varunit vu1 ON vu1.id    = v1.unit
LEFT JOIN pl          ON pl.id     = e.pl
WHERE f_unaccent(p.abrev)   ILIKE f_unaccent('%' || 'vicenti' || '%') OR
      f_unaccent(p.prenome) ILIKE f_unaccent('%' || 'vicenti' || '%');

Poin utama

Mengapa f_unaccent() ? Karena unaccent() tidak dapat diindeks. Baca ini:

Saya menggunakan fungsi yang diuraikan di sana untuk memungkinkan trigram fungsional multikolom berikut (disarankan!) GIN indeks :

CREATE INDEX pess_unaccent_nome_trgm_idx ON pess
USING gin (f_unaccent(pess) gin_trgm_ops, f_unaccent(prenome) gin_trgm_ops);

Jika Anda tidak terbiasa dengan indeks trigram, baca ini dulu:

Dan mungkin:

Pastikan untuk menjalankan Postgres versi terbaru (saat ini 9.5). Ada peningkatan substansial pada indeks GIN. Dan Anda akan tertarik dengan peningkatan pg_trgm 1.2, dijadwalkan akan dirilis bersama Postgres 9.6 mendatang:

Pernyataan yang disiapkan adalah cara umum untuk mengeksekusi kueri dengan parameter (terutama dengan teks dari input pengguna). Postgres harus menemukan rencana yang paling cocok untuk parameter apa pun. Tambahkan karakter pengganti sebagai konstanta ke ke istilah pencarian seperti ini:

f_unaccent(p.abrev) ILIKE f_unaccent('%' || 'vicenti' || '%')

('vicenti' akan diganti dengan parameter.) Jadi Postgres tahu bahwa kita berurusan dengan pola yang tidak berlabuh ke kiri atau ke kanan - yang akan memungkinkan strategi yang berbeda. Jawaban terkait dengan detail lebih lanjut:

Atau mungkin merencanakan ulang kueri untuk setiap istilah pencarian (mungkin menggunakan SQL dinamis dalam suatu fungsi). Namun pastikan waktu perencanaan tidak memakan kemungkinan peningkatan kinerja.

WHERE kondisi kolom di pess bertentangan dengan LEFT JOIN . Postgres terpaksa mengonversinya menjadi INNER JOIN . Yang lebih buruk lagi, join datang terlambat di pohon join. Dan karena Postgres tidak dapat menyusun ulang gabungan Anda (lihat di bawah), itu bisa menjadi sangat mahal. Pindahkan tabel ke pertama posisi di FROM klausa untuk menghilangkan baris lebih awal. Mengikuti LEFT JOIN s tidak menghilangkan baris apa pun menurut definisi. Tetapi dengan banyak tabel, penting untuk memindahkan gabungan yang mungkin berlipat ganda baris sampai akhir.

Anda bergabung dengan 13 tabel, 12 di antaranya dengan LEFT JOIN yang meninggalkan 12! kemungkinan kombinasi - atau 11! * 2! jika kita mengambil yang LEFT JOIN mempertimbangkan bahwa itu benar-benar INNER JOIN . Itu juga banyak untuk Postgres untuk mengevaluasi semua kemungkinan permutasi untuk rencana kueri terbaik. Baca tentang join_collapse_limit :

Pengaturan default untuk join_collapse_limit adalah 8 , yang berarti Postgres tidak akan mencoba menyusun ulang tabel di FROM . Anda klausa dan urutan tabel relevan .

Salah satu cara untuk mengatasinya adalah dengan membagi bagian kinerja-kritis menjadi CTE seperti @joop berkomentar . Jangan setel join_collapse_limit jauh lebih tinggi atau waktu untuk perencanaan kueri yang melibatkan banyak tabel yang digabungkan akan memburuk.

Tentang tanggal gabungan bernama data :

cast(cast(e.ano as varchar(4))||'-'||right('0'||cast(e.mes as varchar(2)),2)||'-'|| right('0'||cast(e.dia as varchar(2)),2) as varchar(10)) as data

Dengan asumsi Anda membangun dari tiga kolom numerik untuk tahun, bulan dan hari, yang didefinisikan NOT NULL , gunakan ini sebagai gantinya:

e.ano::text || to_char(e.mes2, 'FM"-"00')
            || to_char(e.dia,  'FM"-"00') AS data

Tentang FM pengubah pola template:

Tapi sungguh, Anda harus menyimpan tanggal sebagai tipe data date untuk memulai.

Juga disederhanakan:

format('%s (%s)', p.abrev, p.prenome) AS determinador

Tidak akan membuat kueri lebih cepat, tetapi jauh lebih bersih. Lihat format() .

Hal pertama terakhir, semua saran biasa untuk pengoptimalan kinerja berlaku:

Jika Anda melakukan semua ini dengan benar, Anda akan melihat kueri yang jauh lebih cepat untuk semua pola.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Mengapa hanya superuser CREATE EXTENSION hstore, tetapi tidak di Heroku?

  2. PostgreSQL:Ubah PEMILIK pada semua tabel secara bersamaan di PostgreSQL

  3. Permintaan rekaman terbaru yang efisien dengan Postgresql

  4. Memilih semua catatan menggunakan SQL LIMIT dan kueri OFFSET

  5. Cara mengelompokkan berdasarkan minggu di postgresql