Saya punya tabel station_logs
di database PostgreSQL 9.6:
Column | Type |
---------------+-----------------------------+
id | bigint | bigserial
station_id | integer | not null
submitted_at | timestamp without time zone |
level_sensor | double precision |
Indexes:
"station_logs_pkey" PRIMARY KEY, btree (id)
"uniq_sid_sat" UNIQUE CONSTRAINT, btree (station_id, submitted_at)
Saya mencoba untuk mendapatkan nilai terakhir level_sensor
berdasarkan submitted_at
, untuk masing-masing station_id
. Ada sekitar 400 station_id
nilai unik , dan sekitar 20rb baris per hari per station_id
.
Sebelum membuat indeks:
EXPLAIN ANALYZE
SELECT DISTINCT ON(station_id) station_id, submitted_at, level_sensor
FROM station_logs ORDER BY station_id, submitted_at DESC;
Unik (biaya = 4347852.14..4450301.72 baris = 89 lebar = 20) (waktu aktual = 22202.080..27619.167 baris = 98 loop = 1) -> Sortir (biaya = 4347852.14..4399076.93 baris = 20489916 lebar = 20) (waktu aktual = 22202.077..26540.827 baris = 20489812 loop = 1) Sortir Key: station_id, dikirimkan_at DESC Metode Sortir: disk gabungan eksternal: 681040kB -> Pemindaian Seq pada station_logs (biaya = 0,00..598895.16 baris = 20489916 lebar = 20) (waktu aktual = 0,023..3443,587 baris = 20489812 loop = $ Waktu perencanaan: 0,072 ms Waktu pelaksanaan: 27690.644 ms
Membuat indeks:
CREATE INDEX station_id__submitted_at ON station_logs(station_id, submitted_at DESC);
Setelah membuat indeks, untuk permintaan yang sama:
Unik (biaya = 0,56..2156367,51 baris = 89 lebar = 20) (waktu aktual = 0,184..16263,413 baris = 98 loop = 1) -> Pemindaian Indeks menggunakan station_id__submitted_at pada station_logs (biaya = 0,56..2105142,98 baris = 20489812 lebar = 20) (waktu aktual = 0,181..1 $ Waktu perencanaan: 0,206 ms Waktu pelaksanaan: 16263.490 ms
Apakah ada cara untuk membuat kueri ini lebih cepat? Seperti 1 detik misalnya, 16 detik masih terlalu banyak.
Jawaban:
Untuk hanya 400 stasiun, query ini akan menjadi besar-besaran lebih cepat:
dbfiddle di sini
(membandingkan paket untuk kueri ini, alternatif Abelisto, dan asli Anda)
Hasil
EXPLAIN ANALYZE
seperti yang disediakan oleh OP:Satu-satunya indeks yang Anda butuhkan adalah satu Anda buat:
station_id__submitted_at
. TheUNIQUE
kendalauniq_sid_sat
juga melakukan pekerjaan, pada dasarnya. Mempertahankan keduanya tampak seperti pemborosan ruang disk dan kinerja penulisan.Saya menambahkan
NULLS LAST
keORDER BY
dalam permintaan karenasubmitted_at
tidak ditentukanNOT NULL
. Idealnya, jika berlaku!, TambahkanNOT NULL
kendala ke kolomsubmitted_at
, jatuhkan indeks tambahan dan hapusNULLS LAST
dari kueri.Jika
submitted_at
bisaNULL
, buatUNIQUE
indeks ini untuk mengganti indeks Anda saat ini dan batasan unik:Mempertimbangkan:
Ini mengasumsikan tabel terpisah
station
dengan satu baris per relevanstation_id
(biasanya PK) - yang seharusnya Anda miliki. Jika Anda tidak memilikinya, buat itu. Sekali lagi, sangat cepat dengan teknik rCTE ini:Saya menggunakannya di biola juga. Anda bisa menggunakan kueri serupa untuk menyelesaikan tugas Anda secara langsung, tanpa
station
tabel - jika Anda tidak dapat diyakinkan untuk membuatnya.Instruksi terperinci, penjelasan dan alternatif:
Optimalkan indeks
Permintaan Anda harus sangat cepat sekarang. Hanya jika Anda masih perlu mengoptimalkan kinerja baca ...
Mungkin masuk akal untuk menambahkan
level_sensor
sebagai kolom terakhir ke indeks untuk memungkinkan pemindaian hanya indeks , seperti komentar joanolo .Con: Itu membuat indeks lebih besar - yang menambahkan sedikit biaya untuk semua permintaan menggunakannya.
Pro: Jika Anda benar-benar mendapatkan hanya scan indeks dari itu, permintaan di tangan tidak harus mengunjungi halaman tumpukan sama sekali, yang membuatnya sekitar dua kali lebih cepat. Tapi itu mungkin keuntungan yang tidak substansial untuk permintaan yang sangat cepat sekarang.
Namun , saya tidak berharap itu bekerja untuk kasus Anda. Anda menyebutkan:
Biasanya, itu akan menunjukkan beban tulis tanpa henti (1 per
station_id
setiap 5 detik). Dan Anda tertarik dengan baris terbaru . Pemindaian hanya indeks hanya berfungsi untuk menumpuk halaman yang terlihat oleh semua transaksi (bit dalam peta visibilitas diatur). Anda harus menjalankanVACUUM
pengaturan yang sangat agresif agar tabel dapat mengikuti beban penulisan, dan sebagian besar waktu tidak akan berfungsi. Jika asumsi saya benar, hanya pemindaian indeks keluar, jangan tambahkanlevel_sensor
ke indeks.OTOH, jika asumsi saya bertahan, dan meja Anda tumbuh sangat besar , indeks BRIN mungkin membantu. Terkait:
Atau, yang lebih terspesialisasi dan lebih efisien: Indeks parsial hanya untuk penambahan terbaru untuk memotong sebagian besar baris yang tidak relevan:
Pilih stempel waktu yang Anda tahu bahwa baris yang lebih muda harus ada. Anda harus menambahkan
WHERE
kondisi yang cocok ke semua permintaan, seperti:Anda harus menyesuaikan indeks dan kueri dari waktu ke waktu.
Jawaban terkait dengan detail lebih lanjut:
sumber
Coba cara klasik:
dbfiddle
JELASKAN ANALISIS oleh ThreadStarter
sumber