Mengajukan pertanyaan ini, khususnya untuk Postgres, karena memiliki dukungan yang baik untuk indeks R-tree / spasial.
Kami memiliki tabel berikut dengan struktur pohon (model Nested Set) kata-kata dan frekuensinya:
lexikon
-------
_id integer PRIMARY KEY
word text
frequency integer
lset integer UNIQUE KEY
rset integer UNIQUE KEY
Dan pertanyaannya:
SELECT word
FROM lexikon
WHERE lset BETWEEN @Low AND @High
ORDER BY frequency DESC
LIMIT @N
Saya kira indeks penutup pada (lset, frequency, word)
akan berguna tetapi saya merasa itu mungkin tidak berkinerja baik jika ada terlalu banyak lset
nilai dalam (@High, @Low)
kisaran.
Indeks sederhana (frequency DESC)
terkadang juga mencukupi, ketika pencarian menggunakan indeks itu menghasilkan lebih awal @N
baris yang cocok dengan kondisi rentang.
Tetapi tampaknya kinerja sangat tergantung pada nilai parameter.
Apakah ada cara untuk membuatnya berkinerja cepat, terlepas dari apakah rentangnya (@Low, @High)
lebar atau sempit dan terlepas dari apakah kata-kata frekuensi teratas untungnya dalam rentang (sempit) yang dipilih?
Apakah indeks R-tree / spasial membantu?
Menambahkan indeks, menulis ulang kueri, mendesain ulang tabel, tidak ada batasan.
sumber
lset,rset
danword
.Jawaban:
Anda mungkin dapat mencapai kinerja yang lebih baik dengan mencari terlebih dahulu di baris dengan frekuensi lebih tinggi. Ini dapat dicapai dengan 'granulasi' frekuensi dan kemudian melangkah secara prosedural, misalnya sebagai berikut:
-
lexikon
data pengujian dan tiruan:granule
analisis (kebanyakan untuk informasi dan penyetelan):fungsi untuk memindai frekuensi tinggi terlebih dahulu:
hasil (timing mungkin harus diambil dengan sejumput garam tetapi setiap kueri dijalankan dua kali untuk melawan caching apa pun)
pertama menggunakan fungsi yang kami tulis:
dan kemudian dengan pemindaian indeks sederhana:
Tergantung pada data dunia nyata Anda, Anda mungkin ingin memvariasikan jumlah butiran dan fungsi yang digunakan untuk menempatkan baris ke dalamnya. Distribusi frekuensi yang sebenarnya adalah kuncinya di sini, seperti juga nilai yang diharapkan untuk
limit
klausa dan ukuranlset
rentang yang dicari.sumber
width_granule=8
antaragranulae_start
dangranulae_end
dari tingkat sebelumnya?frequency
dihasilkan: kesenjangan besar antara 1e6 / 2 dan 1e6 / 3, semakin tinggi jumlah baris, semakin kecil gap. Bagaimanapun, Terima kasih atas pendekatan yang luar biasa ini !!Mendirikan
Saya sedang membangun pengaturan @ Jack untuk memudahkan orang untuk mengikuti dan membandingkan. Diuji dengan PostgreSQL 9.1.4 .
Dari sini saya mengambil rute yang berbeda:
Meja bantu
Solusi ini tidak menambahkan kolom ke tabel asli, hanya membutuhkan tabel pembantu kecil. Saya menempatkannya di skema
public
, gunakan skema apa pun pilihan Anda.Tabelnya terlihat seperti ini:
Karena kolom
cond
akan digunakan dalam SQL dinamis lebih jauh ke bawah, Anda harus membuat tabel ini aman . Selalu sediakan skema-tabel jika Anda tidak yakin dengan arus yang sesuaisearch_path
, dan cabut hak istimewa menulis daripublic
(dan peran tidak tepercaya lainnya):Tabel ini
lex_freq
memiliki tiga tujuan:Indeks
DO
Pernyataan ini menciptakan semua indeks yang dibutuhkan:Semua indeks parsial ini bersama-sama merentang tabel sekali. Ukurannya hampir sama dengan satu indeks dasar di seluruh tabel:
Sejauh ini, hanya 21 MB indeks untuk 50 MB tabel.
Saya membuat sebagian besar indeks parsial
(lset, frequency DESC)
. Kolom kedua hanya membantu dalam kasus khusus. Tetapi karena kedua kolom yang terlibat adalah tipeinteger
, karena kekhususan penyelarasan data dalam kombinasi dengan MAXALIGN di PostgreSQL, kolom kedua tidak membuat indeks lebih besar. Ini adalah kemenangan kecil tanpa biaya.Tidak ada gunanya melakukan itu untuk indeks parsial yang hanya menjangkau satu frekuensi. Itu baru saja menyala
(lset)
. Indeks yang dibuat terlihat seperti ini:Fungsi
Fungsi ini agak mirip dengan gaya solusi @ Jack:
Perbedaan utama:
SQL dinamis dengan
RETURN QUERY EXECUTE
.Saat kami mengulangi langkah-langkah, rencana kueri yang berbeda mungkin penerima. Rencana kueri untuk SQL statis dihasilkan sekali dan kemudian digunakan kembali - yang dapat menghemat biaya tambahan. Tetapi dalam hal ini kueri itu sederhana dan nilainya sangat berbeda. SQL dinamis akan menjadi kemenangan besar.
Dinamis
LIMIT
untuk setiap langkah kueri.Ini membantu dalam berbagai cara: Pertama, baris hanya diambil sesuai kebutuhan. Dalam kombinasi dengan SQL dinamis, ini juga dapat menghasilkan rencana kueri yang berbeda untuk memulai. Kedua: Tidak perlu tambahan
LIMIT
dalam pemanggilan fungsi untuk memotong surplus.Tolok ukur
Mendirikan
Saya mengambil empat contoh dan menjalankan tiga tes berbeda dengan masing-masing. Saya mengambil yang terbaik dari lima untuk membandingkan dengan cache hangat:
Kueri SQL mentah dari formulir:
Hal yang sama setelah membuat indeks ini
Membutuhkan ruang yang sama dengan semua indeks parsial saya bersama-sama:
Fungsinya
Hasil
1: Total runtime: 315.458 ms
2: Total runtime: 36.458 ms
3: Total runtime: 0.330 ms
1: Total runtime: 294.819 ms
2: Total runtime: 18.915 ms
3: Total runtime: 1.414 ms
1: Total runtime: 426.831 ms
2: Total runtime: 217.874 ms
3: Total runtime: 1.611 ms
1: Total runtime: 2458.205 ms
2: Total runtime: 2458.205 ms - untuk rentang lset yang besar, pemindaian seq lebih cepat daripada indeks.
3: Total runtime: 0,266 ms
Kesimpulan
Seperti yang diharapkan, manfaat dari fungsi tumbuh dengan rentang yang lebih besar
lset
dan lebih kecilLIMIT
.Dengan rentang yang sangat kecil
lset
, kueri mentah dalam kombinasi dengan indeks sebenarnya lebih cepat . Anda ingin menguji dan mungkin bercabang: kueri mentah untuk rentang kecillset
, atau fungsi lainnya panggil. Anda bahkan bisa membuatnya menjadi fungsi untuk "yang terbaik dari kedua dunia" - itulah yang akan saya lakukan.Bergantung pada distribusi data Anda dan pertanyaan umum, langkah-langkah lebih dalam
lex_freq
dapat membantu kinerja. Tes untuk menemukan sweet spot. Dengan alat yang disajikan di sini, seharusnya mudah untuk menguji.sumber
Saya tidak melihat alasan untuk memasukkan kolom kata dalam indeks. Jadi indeks ini
akan membuat kueri Anda berkinerja cepat.
UPD
Saat ini tidak ada cara untuk membuat indeks penutup di PostgreSQL. Ada diskusi tentang fitur ini di milis PostgreSQL http://archives.postgresql.org/pgsql-performance/2012-06/msg00114.php
sumber
Menggunakan indeks GIST
Itu tergantung pada apa yang Anda maksudkan saat berpuasa: Anda jelas harus mengunjungi setiap baris dalam rentang karena permintaan Anda
ORDER freq DESC
. Tidak tahu bahwa perencana permintaan sudah membahas ini jika saya mengerti pertanyaannya,Di sini kita membuat tabel dengan 10k baris
(5::int,random()::double precision)
Kami mengindeksnya,
Kami menanyakannya,
Kami mendapat
Seq Scan on t
. Ini hanya karena perkiraan selektivitas kami membiarkan pg menyimpulkan akses tumpukan lebih cepat daripada memindai indeks dan memeriksa ulang. Jadi kami membuatnya lebih menarik dengan memasukkan 1.000.000 baris lebih(42::int,random()::double precision)
yang tidak sesuai dengan "jangkauan" kami.Dan kemudian kita meminta,
Anda dapat melihat di sini kami menyelesaikan dalam 4.6 MS dengan Scan Indeks Saja ,
Memperluas rentang untuk menyertakan seluruh tabel, menghasilkan pemindaian seq lain - secara logis, dan menumbuhkannya dengan miliaran baris lainnya akan menghasilkan Pemindaian Indeks lainnya.
Jadi dalam ringkasan,
sumber