Saya mencoba menentukan indeks mana yang akan digunakan untuk permintaan SQL dengan WHERE
kondisi dan GROUP BY
yang saat ini berjalan sangat lambat.
Permintaan saya:
SELECT group_id
FROM counter
WHERE ts between timestamp '2014-03-02 00:00:00.0' and timestamp '2014-03-05 12:00:00.0'
GROUP BY group_id
Tabel saat ini memiliki 32.000.000 baris. Waktu eksekusi permintaan meningkat banyak ketika saya meningkatkan kerangka waktu.
Tabel tersebut terlihat seperti ini:
CREATE TABLE counter (
id bigserial PRIMARY KEY
, ts timestamp NOT NULL
, group_id bigint NOT NULL
);
Saat ini saya memiliki indeks berikut, tetapi kinerjanya masih lambat:
CREATE INDEX ts_index
ON counter
USING btree
(ts);
CREATE INDEX group_id_index
ON counter
USING btree
(group_id);
CREATE INDEX comp_1_index
ON counter
USING btree
(ts, group_id);
CREATE INDEX comp_2_index
ON counter
USING btree
(group_id, ts);
Menjalankan EXPLAIN pada kueri memberikan hasil berikut:
"QUERY PLAN"
"HashAggregate (cost=467958.16..467958.17 rows=1 width=4)"
" -> Index Scan using ts_index on counter (cost=0.56..467470.93 rows=194892 width=4)"
" Index Cond: ((ts >= '2014-02-26 00:00:00'::timestamp without time zone) AND (ts <= '2014-02-27 23:59:00'::timestamp without time zone))"
SQL Fiddle dengan contoh data: http://sqlfiddle.com/#!15/7492b/1
Pertanyaan
Dapatkah kinerja kueri ini ditingkatkan dengan menambahkan indeks yang lebih baik, atau haruskah saya meningkatkan kekuatan pemrosesan?
Edit 1
PostgreSQL versi 9.3.2 digunakan.
Edit 2
Saya mencoba proposal @Erwin dengan EXISTS
:
SELECT group_id
FROM groups g
WHERE EXISTS (
SELECT 1
FROM counter c
WHERE c.group_id = g.group_id
AND ts BETWEEN timestamp '2014-03-02 00:00:00'
AND timestamp '2014-03-05 12:00:00'
);
Namun sayangnya ini tampaknya tidak meningkatkan kinerja. Rencana Kueri:
"QUERY PLAN"
"Nested Loop Semi Join (cost=1607.18..371680.60 rows=113 width=4)"
" -> Seq Scan on groups g (cost=0.00..2.33 rows=133 width=4)"
" -> Bitmap Heap Scan on counter c (cost=1607.18..158895.53 rows=60641 width=4)"
" Recheck Cond: ((group_id = g.id) AND (ts >= '2014-01-01 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"
" -> Bitmap Index Scan on comp_2_index (cost=0.00..1592.02 rows=60641 width=0)"
" Index Cond: ((group_id = g.id) AND (ts >= '2014-01-01 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"
Edit 3
Paket permintaan untuk permintaan LATERAL dari ypercube:
"QUERY PLAN"
"Nested Loop (cost=8.98..1200.42 rows=133 width=20)"
" -> Seq Scan on groups g (cost=0.00..2.33 rows=133 width=4)"
" -> Result (cost=8.98..8.99 rows=1 width=0)"
" One-Time Filter: ($1 IS NOT NULL)"
" InitPlan 1 (returns $1)"
" -> Limit (cost=0.56..4.49 rows=1 width=8)"
" -> Index Only Scan using comp_2_index on counter c (cost=0.56..1098691.21 rows=279808 width=8)"
" Index Cond: ((group_id = $0) AND (ts IS NOT NULL) AND (ts >= '2010-03-02 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"
" InitPlan 2 (returns $2)"
" -> Limit (cost=0.56..4.49 rows=1 width=8)"
" -> Index Only Scan Backward using comp_2_index on counter c_1 (cost=0.56..1098691.21 rows=279808 width=8)"
" Index Cond: ((group_id = $0) AND (ts IS NOT NULL) AND (ts >= '2010-03-02 00:00:00'::timestamp without time zone) AND (ts <= '2014-03-05 12:00:00'::timestamp without time zone))"
group_id
nilai berbeda yang ada di atas meja?group_id
dan tidak masuk hitungan?Jawaban:
Gagasan lain, yang juga menggunakan
groups
tabel dan konstruksi yang disebutLATERAL
join (untuk penggemar SQL-Server, ini hampir identik denganOUTER APPLY
). Ini memiliki keuntungan bahwa agregat dapat dihitung dalam subquery:Tes di SQL-Fiddle menunjukkan bahwa kueri melakukan pemindaian indeks pada
(group_id, ts)
indeks.Rencana serupa diproduksi menggunakan 2 gabungan lateral, satu untuk min dan satu untuk maks dan juga dengan 2 subquery berkorelasi sebaris. Mereka juga dapat digunakan jika Anda perlu menunjukkan seluruh
counter
baris selain tanggal min dan maks:sumber
Karena Anda tidak memiliki agregat dalam daftar pilih, maka
group by
hampir sama dengan menempatkan adistinct
dalam daftar pilih, kan?Jika itu yang Anda inginkan, Anda mungkin bisa mendapatkan pencarian indeks cepat di comp_2_index dengan menulis ulang ini untuk menggunakan permintaan rekursif, seperti yang dijelaskan pada wiki PostgreSQL .
Buat tampilan untuk secara efisien mengembalikan group_ids yang berbeda:
Dan kemudian gunakan tampilan itu di tempat tabel pencarian di
exists
semi-join Erwin .sumber
Karena hanya ada
133 different group_id's
, Anda dapat menggunakaninteger
(atau bahkansmallint
) untuk group_id. Ini tidak akan membeli banyak, karena padding hingga 8 byte akan memakan sisanya di meja Anda dan kemungkinan indeks multikolom. Pemrosesan polosinteger
harus sedikit lebih cepat. Lebih lanjut tentangint
vsint2
.@ Leo: cap waktu disimpan sebagai bilangan bulat 8-byte dalam instalasi modern dan dapat diproses dengan sangat cepat. Detail
@ ypercube: Indeks aktif
(group_id, ts)
tidak dapat membantu, karena tidak ada kondisi padagroup_id
kueri.Masalah utama Anda adalah banyaknya data yang harus diproses:
Saya melihat Anda hanya tertarik pada keberadaan
group_id
, dan tidak ada hitungan yang sebenarnya. Juga, hanya ada 133group_id
s berbeda . Karenanya, kueri Anda dapat dipenuhi dengan klik pertama pergorup_id
dalam kerangka waktu. Karenanya saran ini untuk kueri alternatif denganEXISTS
semi-gabung :Dengan asumsi tabel pencarian untuk grup:
Indeks Anda
comp_2_index
di(group_id, ts)
menjadi instrumen sekarang.SQL Fiddle (membangun biola yang disediakan oleh @ypercube di komentar)
Di sini, kueri lebih memilih indeks aktif
(ts, group_id)
, tapi saya pikir itu karena pengaturan tes dengan cap waktu "berkerumun". Jika Anda menghapus indeks dengan memimpints
( lebih lanjut tentang itu ), perencana dengan senang hati akan menggunakan indeks(group_id, ts)
juga - terutama dalam Pemindaian Indeks Saja .Jika berhasil, Anda mungkin tidak memerlukan peningkatan lain yang mungkin: data pra-agregat dalam tampilan terwujud untuk secara drastis mengurangi jumlah baris. Ini akan masuk akal secara khusus, jika Anda juga membutuhkan penghitungan aktual tambahan. Maka Anda memiliki biaya untuk memproses banyak baris sekaligus saat memperbarui mv. Anda bahkan dapat menggabungkan agregat harian dan per jam (dua tabel terpisah) dan menyesuaikan kueri Anda dengan itu.
Apakah kerangka waktu dalam kueri Anda sewenang-wenang? Atau sebagian besar pada menit / jam / hari penuh?
Buat indeks yang diperlukan pada
counter_mv
dan sesuaikan kueri Anda untuk bekerja dengannya ...sumber
groups
tabel membuat perbedaan?ANALYZE
membuat perbedaan. Tetapi indeks padacounter
bahkan digunakan tanpaANALYZE
segera setelah saya memperkenalkangroups
tabel. Intinya adalah, tanpa tabel itu, seqscan tetap diperlukan untuk membangun set group_id yang mungkin. Saya menambahkan lebih banyak ke jawaban saya. Dan terima kasih untuk biola Anda!group_id
bahkan untukSELECT DISTINCT group_id FROM t;
permintaan?LIMIT 1
, ia dapat memilih pemindaian indeks bitmap, yang tidak mendapat manfaat dari penghentian dini dan membutuhkan waktu lebih lama. (Tetapi jika tabel tersebut baru disedot, mungkin lebih suka pemindaian indeks saja dari pemindaian bitmap, jadi perilaku mana yang Anda lihat tergantung pada status vakum dari tabel).