Query fulltext terhadap database ini (menyimpan tiket RT ( Request Tracker )) tampaknya membutuhkan waktu yang sangat lama untuk dieksekusi. Tabel lampiran (berisi data teks lengkap) sekitar 15GB.
Skema database adalah sebagai berikut, ini sekitar 2 juta baris:
rt4 = # \ d + lampiran Tabel "public.attachments" Kolom | Ketik | Pengubah | Penyimpanan | Deskripsi ----------------- + ----------------------------- + - -------------------------------------------------- ------ + ---------- + ------------- id | integer | bukan null default default ('attachments_id_seq' :: regclass) | polos | transactionid | integer | bukan nol | polos | induk | integer | bukan nol default 0 | polos | messageid | karakter bervariasi (160) | | diperpanjang | subjek | karakter bervariasi (255) | | diperpanjang | nama file | karakter bervariasi (255) | | diperpanjang | contenttype | karakter bervariasi (80) | | diperpanjang | contentencoding | karakter bervariasi (80) | | diperpanjang | konten | teks | | diperpanjang | header | teks | | diperpanjang | pencipta | integer | bukan nol default 0 | polos | dibuat | timestamp tanpa zona waktu | | polos | contentindex | tsvector | | diperpanjang | Indeks: "attachments_pkey" KUNCI UTAMA, btree (id) "attachments1" btree (parent) "attachments2" btree (transactionid) "attachments3" btree (parent, transactionid) "contentindex_idx" gin (contentindex) Memiliki OID: tidak
Saya dapat melakukan query pada database sendiri dengan sangat cepat (<1s) dengan permintaan seperti:
select objectid
from attachments
join transactions on attachments.transactionid = transactions.id
where contentindex @@ to_tsquery('frobnicate');
Namun, ketika RT menjalankan kueri yang seharusnya melakukan pencarian indeks teks lengkap pada tabel yang sama, biasanya dibutuhkan ratusan detik untuk menyelesaikannya. Output analisis kueri adalah sebagai berikut:
Pertanyaan
SELECT COUNT(DISTINCT main.id)
FROM Tickets main
JOIN Transactions Transactions_1 ON ( Transactions_1.ObjectType = 'RT::Ticket' )
AND ( Transactions_1.ObjectId = main.id )
JOIN Attachments Attachments_2 ON ( Attachments_2.TransactionId = Transactions_1.id )
WHERE (main.Status != 'deleted')
AND ( ( ( Attachments_2.ContentIndex @@ plainto_tsquery('frobnicate') ) ) )
AND (main.Type = 'ticket')
AND (main.EffectiveId = main.id);
EXPLAIN ANALYZE
keluaran
RENCANA QUERY -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------- Agregat (biaya = 51210.60..51210.61 baris = 1 lebar = 4) (waktu aktual = 477778.806..477778.806 baris = 1 loop = 1) -> Nested Loop (biaya = 0,00..51210.57 baris = 15 lebar = 4) (waktu aktual = 17943.986..477775.174 baris = 4197 loop = 1) -> Nested Loop (biaya = 0,00..40643.08 baris = 6507 lebar = 8) (waktu aktual = 8.526..20610.380 baris = 1714818 loop = 1) -> Seq Memindai pada tiket utama (biaya = 0,00..9818,37 baris = 598 lebar = 8) (waktu aktual = 0,008..256,042 baris = 96990 loop = 1) Saring: (((status) :: teks 'dihapus' :: teks) DAN (id = efektifid) DAN ((tipe) :: teks = 'tiket' :: teks)) -> Pemindaian Indeks menggunakan transaksi1 pada transaksi transaksi_1 (biaya = 0,00..51,36 baris = 15 lebar = 8) (waktu aktual = 0,102..0.202 baris = 18 loop = 96990) Indeks Cond: (((objecttype) :: text = 'RT :: Ticket' :: text) AND (objectid = main.id)) -> Pemindaian Indeks menggunakan attachments2 pada attachments attachments_2 (biaya = 0,00..1.61 baris = 1 lebar = 4) (waktu aktual = 0.266..0.266 baris = 0 loop = 1714818) Indeks Cond: (transactionid = transaction_1.id) Saring: (contentindex @@ plainto_tsquery ('frobnicate' :: text)) Total runtime: 477778.883 ms
Sejauh yang saya tahu, masalahnya tampaknya bukan menggunakan indeks yang dibuat di contentindex
lapangan ( contentindex_idx
), melainkan melakukan filter pada sejumlah besar baris yang cocok di tabel lampiran. Jumlah baris dalam output yang dijelaskan juga tampak sangat tidak akurat, bahkan setelah yang terbaru ANALYZE
: baris yang diestimasi = 6507 baris aktual = 1714818.
Saya tidak begitu yakin ke mana harus pergi dengan ini.
Jawaban:
Ini dapat ditingkatkan dalam seribu satu cara, maka itu harus dalam hitungan milidetik .
Pertanyaan yang lebih baik
Ini hanya permintaan Anda yang diformat ulang dengan alias dan beberapa derau dihapus untuk menghapus kabut:
Sebagian besar masalah dengan kueri Anda terletak pada dua tabel pertama
tickets
dantransactions
, yang hilang dari pertanyaan. Saya mengisi dengan tebakan yang terpelajar.t.status
,t.objecttype
dantr.objecttype
mungkin seharusnya tidaktext
, tetapienum
atau mungkin beberapa nilai yang sangat kecil merujuk tabel pencarian.EXISTS
setengah bergabungDengan asumsi
tickets.id
adalah kunci utama, formulir yang ditulis ulang ini harus jauh lebih murah:Alih-alih mengalikan baris dengan dua 1: n bergabung, hanya untuk menciutkan beberapa pertandingan pada akhirnya dengan
count(DISTINCT id)
, gunakanEXISTS
semi-gabung, yang dapat berhenti mencari lebih jauh begitu pertandingan pertama ditemukan dan pada saat yang sama mengacaukanDISTINCT
langkah terakhir . Per dokumentasi:Efektivitas tergantung pada berapa banyak transaksi per tiket dan lampiran per transaksi yang ada.
Tentukan urutan bergabung dengan
join_collapse_limit
Jika Anda tahu bahwa istilah pencarian Anda untuk
attachments.contentindex
yang sangat selektif - lebih selektif dibandingkan kondisi lain di query (yang mungkin kasus untuk 'frobnicate', tetapi tidak untuk 'masalah'), Anda dapat memaksa urutan bergabung. Perencana kueri tidak dapat menilai selektivitas kata tertentu, kecuali yang paling umum. Per dokumentasi:Gunakan
SET LOCAL
untuk tujuan hanya mengaturnya untuk transaksi saat ini.Urutan
WHERE
kondisi selalu tidak relevan. Hanya urutan gabungan yang relevan di sini.Atau gunakan CTE seperti @jjanes menjelaskan dalam "Opsi 2". untuk efek yang serupa.
Indeks
Indeks B-tree
Ambil semua kondisi
tickets
yang digunakan secara identik dengan sebagian besar kueri dan buat indeks parsial padatickets
:Jika salah satu kondisi adalah variabel, jatuhkan dari
WHERE
kondisi dan tambahkan kolom sebagai kolom indeks.Satu lagi di
transactions
:Kolom ketiga hanya untuk mengaktifkan pemindaian hanya indeks.
Juga, karena Anda memiliki indeks komposit ini dengan dua kolom bilangan bulat di
attachments
:Indeks tambahan ini adalah pemborosan total , hapus:
Detail:
Indeks GIN
Tambahkan
transactionid
ke indeks GIN Anda untuk membuatnya jauh lebih efektif. Ini mungkin peluru perak lain , karena berpotensi memungkinkan scan indeks saja, menghilangkan kunjungan ke meja besar sepenuhnya.Anda memerlukan kelas operator tambahan yang disediakan oleh modul tambahan
btree_gin
. Instruksi terperinci:4 byte dari
integer
kolom tidak membuat indeks lebih besar. Juga, untungnya bagi Anda, indeks GIN berbeda dari indeks B-tree dalam aspek penting. Per dokumentasi:Penekanan berani saya. Jadi Anda hanya perlu satu indeks GIN (besar dan agak mahal).
Definisi tabel
Pindahkan
integer not null columns
ke depan. Ini memiliki beberapa efek positif kecil pada penyimpanan dan kinerja. Menghemat 4 - 8 byte per baris dalam hal ini.sumber
Pilihan 1
Perencana tidak memiliki wawasan tentang sifat sebenarnya dari hubungan antara EffectiveId dan id, dan mungkin berpikir klausa:
akan menjadi jauh lebih selektif daripada yang sebenarnya. Jika ini yang saya pikirkan, EffectiveID hampir selalu sama dengan main.id, tetapi perencana tidak mengetahuinya.
Cara yang mungkin lebih baik untuk menyimpan jenis hubungan ini adalah dengan menentukan nilai NULL dari EffectiveID yang berarti "secara efektif sama dengan id", dan menyimpan sesuatu di dalamnya hanya jika ada perbedaan.
Dengan asumsi Anda tidak ingin mengatur ulang skema Anda, Anda dapat mencoba menyiasatinya dengan menulis ulang klausa itu sebagai sesuatu seperti:
Perencana mungkin berasumsi bahwa
between
itu kurang selektif daripada kesetaraan, dan itu mungkin cukup untuk mengeluarkannya dari perangkap saat ini.pilihan 2
Pendekatan lain adalah dengan menggunakan CTE:
Ini memaksa perencana untuk menggunakan ContentIndex sebagai sumber selektivitas. Setelah dipaksa untuk melakukan itu, korelasi kolom yang menyesatkan pada tabel Tiket tidak akan lagi terlihat begitu menarik. Tentu saja jika seseorang mencari 'masalah' daripada 'frobnicate', itu mungkin menjadi bumerang.
Opsi 3
Untuk menyelidiki perkiraan baris buruk lebih lanjut, Anda harus menjalankan kueri di bawah ini dalam semua 2 ^ 3 = 8 permutasi dari berbagai klausa DAN yang dikomentari. Ini akan membantu mencari tahu dari mana perkiraan buruk itu berasal.
sumber