Bagaimana mempercepat pilih yang berbeda?

16

Saya memiliki beberapa pilihan sederhana pada beberapa data deret waktu:

SELECT DISTINCT user_id
FROM events
WHERE project_id = 6
AND time > '2015-01-11 8:00:00'
AND time < '2015-02-10 8:00:00';

Dan itu membutuhkan 112 detik. Inilah rencana kueri:

http://explain.depesz.com/s/NTyA

Aplikasi saya harus membentuk banyak operasi berbeda dan jumlah seperti ini. Apakah ada cara yang lebih cepat untuk mendapatkan data seperti ini?

Sam
sumber

Jawaban:

19

Anda mungkin tidak ingin mendengar ini, tetapi opsi terbaik untuk mempercepat SELECT DISTINCTadalah menghindari DISTINCT untuk memulainya. Dalam banyak kasus (tidak semua!) Dapat dihindari dengan desain database yang lebih baik atau pertanyaan yang lebih baik.

Terkadang, GROUP BYlebih cepat, karena dibutuhkan jalur kode yang berbeda.

Dalam kasus khusus Anda , sepertinya Anda tidak bisa menyingkirkannya DISTINCT. Tetapi Anda dapat mendukung kueri dengan indeks khusus jika Anda memiliki banyak pertanyaan seperti itu:

CREATE INDEX foo ON events (project_id, "time", user_id);

Menambahkan user_idhanya berguna jika Anda hanya dapat memindai indeks saja . Ikuti tautan untuk detailnya. Akan menghapus Pemindaian Tumpukan Bitmap yang mahal dari paket kueri Anda, yang menghabiskan 90% waktu permintaan.

EXPLAINOutput Anda memberi tahu saya bahwa kueri harus memadatkan 2.491 pengguna yang berbeda dari setengah juta baris yang cocok. Ini tidak akan menjadi super cepat, apa pun yang Anda lakukan, tetapi ini bisa jauh lebih cepat.

Jika interval waktu dalam kueri Anda selalu sama, MATERIALIIZED VIEWlipat user_idper (project_id, <fixed time intervall>)akan jauh. Tidak ada kesempatan di sana dengan interval waktu yang bervariasi. Mungkin Anda bisa setidaknya melipat pengguna per jam atau unit waktu minimum lainnya, dan itu akan membeli kinerja yang cukup untuk menjamin overhead yang cukup besar.

Nitpick:
Kemungkinan besar, predikat pada "time"seharusnya adalah:

AND "time" >= '2015-01-11 8:00:00'
AND "time" <  '2015-02-10 8:00:00';

Selain:
Jangan gunakan timesebagai pengidentifikasi. Ini adalah kata yang dilindungi undang - undang dalam SQL standar dan tipe dasar dalam Postgres.

Erwin Brandstetter
sumber
Saya telah membaca sedikit tentang scan indeks saja, saya akan mencobanya.
Sam
Sayangnya, interval waktu tidak tetap.
Sam
@ Sam: Jadi seberapa cepat kueri contoh Anda dengan indeks yang disarankan?
Erwin Brandstetter
3
@edwin: Belum mencoba produksi. Namun, saya menjalankan kueri asli pada lokal saya (dengan data yang sama) dan butuh 3678.780 ms. Kemudian saya menambahkan indeks dan mempercepatnya hingga 170,156 ms. Paket sekarang berisi 'Pindaian Hanya Indeks menggunakan foo on events'.
Sam
1
@ Sam: Bagus! Itulah yang saya tuju.
Erwin Brandstetter
2

Ini tes saya pada kasus Sam dan jawaban Erwin

drop table t1
create table t1 (id int, user_id int, project_id int, date_time timestamp without time zone) ;

insert into t1 -- 10 million row - size="498 MB"
select row_number() over(), round(row_number() over()/1000), round(row_number() over()/100000) , date
from generate_series('2015-01-01'::date, '2016-12-01'::date,'6 seconds'::interval
) date 
limit 10000000

-- before indexing - 10000000 row - output=100 row - time=2900ms
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 8:00:00'
AND date_time < '2016-12-01 8:00:00' ;

CREATE INDEX foo ON t1 (project_id, date_time, user_id); -- time process=51.2 secs -- size="387 MB"         

-- after indexing - 10000000 row - output=100 row - time= 75ms (reduce ~ 38 times)
SELECT DISTINCT user_id
FROM t1
WHERE project_id = 1
AND date_time > '2015-01-01 00:00:00'
AND date_time < '2016-12-01 00:00:00' ;

Erwin berkata, "Anda mungkin tidak ingin mendengar ini, tetapi opsi terbaik untuk mempercepat SELECT DISTINCT adalah dengan menghindari DISTINCT untuk memulai. Dalam banyak kasus (tidak semua!) Itu dapat dihindari dengan desain database yang lebih baik atau pertanyaan yang lebih baik ". Saya pikir dia benar, kita harus menghindari menggunakan "berbeda, kelompok, memesan" (jika ada).

Saya bertemu situasi sebagai kasus Sam dan saya pikir Sam dapat menggunakan partisi pada tabel acara per bulan. Ini akan mengurangi ukuran data Anda ketika Anda query, tetapi Anda membutuhkan fungsi (pl / pgsql) untuk mengeksekusi alih-alih query di atas. Fungsi ini akan menemukan partisi yang sesuai (tergantung kondisi) untuk mengeksekusi query.

Luan Huynh
sumber
2
> Saya pikir dia benar, kita harus menghindari penggunaan "berbeda, kelompok demi, dipesan" - dan juga SELECT, INSERT, dan UPDATE. Jika kita menghindari konstruksi ini, basis data kita akan sangat cepat!
greatvovan