Saya punya meja dengan 7,2 juta tupel yang terlihat seperti ini:
table public.methods
column | type | attributes
--------+-----------------------+----------------------------------------------------
id | integer | not null DEFAULT nextval('methodkey'::regclass)
hash | character varying(32) | not null
string | character varying | not null
method | character varying | not null
file | character varying | not null
type | character varying | not null
Indexes:
"methods_pkey" PRIMARY KEY, btree (id)
"methodhash" btree (hash)
Sekarang saya ingin memilih beberapa nilai tetapi permintaannya sangat lambat:
db=# explain
select hash, string, count(method)
from methods
where hash not in
(select hash from nostring)
group by hash, string
order by count(method) desc;
QUERY PLAN
----------------------------------------------------------------------------------------
Sort (cost=160245190041.10..160245190962.07 rows=368391 width=182)
Sort Key: (count(methods.method))
-> GroupAggregate (cost=160245017241.77..160245057764.73 rows=368391 width=182)
-> Sort (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
Sort Key: methods.hash, methods.string
-> Seq Scan on methods (cost=0.00..160243305942.27 rows=3683905 width=182)
Filter: (NOT (SubPlan 1))
SubPlan 1
-> Materialize (cost=0.00..41071.54 rows=970636 width=33)
-> Seq Scan on nostring (cost=0.00..28634.36 rows=970636 width=33)
The hash
kolom adalah hash md5 dari string
dan memiliki indeks. Jadi saya pikir masalah saya adalah bahwa seluruh tabel diurutkan berdasarkan id dan bukan oleh hash, jadi butuh beberapa saat untuk mengurutkannya terlebih dahulu lalu mengelompokkannya?
Tabel nostring
hanya berisi daftar hash yang tidak ingin saya miliki. Tapi saya butuh kedua tabel untuk memiliki semua nilai. Jadi ini bukan opsi untuk menghapus ini.
info tambahan: tidak ada kolom yang bisa nol (perbaiki dalam definisi tabel) dan saya menggunakan postgresql 9.2.
NULL
nilai dalam kolommethod
? Apakah ada duplikat aktifstring
?Jawaban:
Jawaban
LEFT JOIN
in @ dezso seharusnya bagus. Akan tetapi, suatu indeks tidak akan berguna (per se), karena kueri harus tetap membaca seluruh tabel - kecuali hanya scan indeks-saja di Postgres 9.2+ dan kondisi yang menguntungkan, lihat di bawah.Jalankan
EXPLAIN ANALYZE
pada kueri. Beberapa kali tidak termasuk efek pencairan dan kebisingan. Bandingkan hasil terbaik.Buat indeks multi-kolom yang cocok dengan kueri Anda:
Tunggu? Setelah saya katakan indeks tidak akan membantu? Kita membutuhkannya
CLUSTER
di meja:Jalankan kembali
EXPLAIN ANALYZE
. Lebih cepat? Harus.CLUSTER
adalah operasi satu kali untuk menulis ulang seluruh tabel dalam urutan indeks yang digunakan. Ini juga efektif aVACUUM FULL
. Jika Anda ingin memastikan, Anda akan menjalankan pre-testVACUUM FULL
sendirian untuk melihat apa yang dapat dikaitkan dengan itu.Jika tabel Anda melihat banyak operasi penulisan, efeknya akan menurun seiring waktu. Jadwalkan
CLUSTER
di luar jam untuk mengembalikan efek. Penyesuaian yang baik tergantung dari kasus penggunaan yang tepat. Manual tentangCLUSTER
.CLUSTER
adalah alat yang agak kasar, membutuhkan kunci eksklusif di atas meja. Jika Anda tidak mampu membelinya, pertimbangkanpg_repack
yang dapat melakukan hal yang sama tanpa kunci eksklusif. Lebih banyak di jawaban nanti:Jika persentase
NULL
nilai dalam kolommethod
tinggi (lebih dari ~ 20 persen, tergantung pada ukuran baris aktual), sebagian indeks akan membantu:(Pembaruan Anda nanti menunjukkan kolom Anda menjadi
NOT NULL
, jadi tidak berlaku.)Jika Anda menjalankan PostgreSQL 9.2 atau yang lebih baru (seperti yang dikomentari @deszo ) indeks yang disajikan mungkin berguna tanpa
CLUSTER
jika perencana dapat menggunakan scan hanya indeks . Hanya berlaku di bawah kondisi yang menguntungkan: Tidak ada operasi penulisan yang akan memengaruhi peta visibilitas karena kolom terakhirVACUUM
dan semua dalam kueri harus dicakup oleh indeks. Pada dasarnya tabel read-only dapat menggunakan ini kapan saja, sementara tabel yang banyak ditulis terbatas. Lebih detail di Postgres Wiki.Indeks parsial yang disebutkan di atas bisa lebih berguna dalam kasus itu.
Jika , di sisi lain, tidak ada
NULL
nilai dalam kolommethod
, Anda harus1.) mendefinisikannya
NOT NULL
dan2.) menggunakannya
count(*)
sebagai gantinyacount(method)
, itu sedikit lebih cepat dan melakukan hal yang sama tanpa adanyaNULL
nilai.Jika Anda harus sering memanggil kueri ini dan tabel ini hanya baca, buat a
MATERIALIZED VIEW
.Titik halus eksotis: Tabel Anda dinamai
nostring
, namun tampaknya mengandung hash. Dengan mengecualikan hash alih-alih string, ada kemungkinan Anda mengecualikan lebih banyak string daripada yang dimaksudkan. Sangat tidak mungkin, tetapi mungkin.sumber
Selamat datang di DBA.SE!
Anda dapat mencoba untuk mengurutkan kembali kueri Anda seperti ini:
atau kemungkinan lain:
NOT IN
adalah wastafel umum untuk kinerja karena sulit untuk menggunakan indeks dengannya.Ini dapat lebih ditingkatkan dengan indeks. Indeks pada
nostring.hash
terlihat bermanfaat. Tetapi pertama-tama: apa yang Anda dapatkan sekarang? (Akan lebih baik untuk melihat outputEXPLAIN ANALYZE
karena biayanya sendiri tidak memberi tahu waktu operasi berlangsung.)sumber
EXPLAIN ANALYZE
.Karena hash adalah md5, Anda mungkin dapat mencoba mengubahnya dalam angka: Anda dapat menyimpannya sebagai angka, atau hanya membuat indeks fungsional yang menghitung angka itu dalam fungsi yang tidak dapat diubah.
Orang lain sudah membuat fungsi pl / pgsql yang mengubah (bagian dari) nilai md5 dari teks ke string. Lihat /programming/9809381/hashing-a-string-to-a-numeric-value-in-postgressql untuk contoh
Saya percaya bahwa Anda benar-benar menghabiskan banyak waktu dalam perbandingan string saat memindai indeks. Jika Anda berhasil menyimpan nilai itu sebagai angka, maka itu harus benar-benar lebih cepat.
sumber
Saya sering mengalami masalah ini, dan menemukan trik 2 bagian sederhana.
Buat indeks substring pada nilai hash: (7 biasanya panjang yang baik)
create index methods_idx_hash_substring ON methods(substring(hash,1,7))
Suruh pencarian Anda / gabung menyertakan kecocokan substring, sehingga perencana kueri disarankan untuk menggunakan indeks:
tua:
WHERE hash = :kwarg
baru:
WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))
Anda juga harus memiliki indeks mentah
hash
.hasilnya (biasanya) adalah perencana yang akan berkonsultasi dengan indeks substring terlebih dahulu dan menyingkirkan sebagian besar baris. kemudian cocok dengan hash 32 karakter penuh dengan indeks (atau tabel) yang sesuai. pendekatan ini telah menurunkan 800ms pertanyaan menjadi 4 untuk saya.
sumber