Kami melanjutkan serangkaian artikel yang ditujukan untuk mempelajari cara-cara yang kurang dikenal untuk meningkatkan kinerja pertanyaan PostgreSQL "tampaknya sederhana":
Jangan berpikir bahwa saya tidak terlalu suka BERGABUNG ... :)
Tetapi seringkali tanpa itu, permintaan secara signifikan lebih produktif daripada bersamanya. Oleh karena itu, hari ini kami akan berusaha sepenuhnya untuk menyapu
habis sumber daya yang intensif - dengan bantuan kamus.

Dimulai dengan PostgreSQL 12, beberapa situasi yang dijelaskan di bawah ini dapat dimainkan sedikit berbeda karena non-materialisasi CTE secara default . Perilaku ini dapat dikembalikan ke dengan menggunakan kunci MATERIALIZED
.
Banyak "fakta" pada kosakata terbatas
Mari kita ambil aplikasi yang sangat nyata - Anda perlu mendaftar
pesan masuk atau tugas aktif dengan pengirim:
25.01 | .. | . 22.01 | .. | : JOIN. 20.01 | .. | . 18.01 | .. | : JOIN . 16.01 | .. | .
Dalam dunia abstrak, penulis tugas harus didistribusikan secara merata di antara semua karyawan organisasi kami, tetapi dalam kenyataannya,
tugas datang, sebagai suatu peraturan, dari jumlah orang yang cukup terbatas - "dari atasan" ke atas hierarki atau "dari sekutu" dari departemen tetangga (analis, desainer pemasaran ...).
Mari kita asumsikan bahwa dalam organisasi kami yang terdiri dari 1000 orang, hanya 20 penulis (biasanya bahkan lebih sedikit) yang menetapkan tugas untuk setiap artis tertentu dan
menggunakan pengetahuan subjek ini untuk mempercepat permintaan "tradisional".
Kami menampilkan 100 tugas terakhir untuk artis tertentu:
SELECT task.* , person.name FROM task LEFT JOIN person ON person.id = task.author_id WHERE owner_id = 777 ORDER BY task_date DESC LIMIT 100;
[lihat menjelaskan.tensor.ru]Ternyata
1/3 dari seluruh waktu dan 3/4 pembacaan halaman data dibuat hanya untuk mencari penulis 100 kali - untuk setiap tugas yang ditampilkan. Tetapi kita tahu bahwa di antara ratusan ini
hanya ada 20 yang berbeda - dapatkah pengetahuan ini digunakan?
kamus hstore
Kami menggunakan
tipe hstore untuk menghasilkan "kamus" nilai kunci:
CREATE EXTENSION hstore
Cukup bagi kami untuk memasukkan ID penulis dan namanya di kamus, sehingga nanti kami bisa mengekstrak menggunakan kunci ini:
[lihat menjelaskan.tensor.ru]Butuh
2 kali lebih sedikit waktu untuk mendapatkan informasi tentang orang
dan 7 kali lebih sedikit data dibaca ! Selain "penipuan", hasil ini membantu kami untuk mencapai
ekstraksi massal catatan dari tabel dalam satu pass menggunakan
= ANY(ARRAY(...))
.
Entri tabel: serialisasi dan deserialisasi
Tetapi bagaimana jika kita perlu menyimpan dalam kamus bukan satu bidang teks, tetapi seluruh catatan? Dalam hal ini, kemampuan PostgreSQL
untuk bekerja dengan menulis tabel sebagai nilai tunggal akan membantu kami:
... , dict AS ( SELECT hstore( array_agg(id)::text[] , array_agg(p)::text[]
Mari kita lihat apa yang terjadi di sini:
- Kami mengambil p sebagai alias untuk catatan lengkap dari tabel orang dan mengumpulkan array dari mereka.
- Larik entri ini disusun kembali menjadi larik string teks (orang [] :: teks []) untuk memasukkannya ke dalam kamus hstore sebagai larik nilai.
- Setelah menerima catatan tertaut, kami mengeluarkannya dari kamus dengan kunci sebagai string teks.
- Kita perlu mengubah teks menjadi nilai dari tipe tabel orang (untuk setiap tabel jenis nama yang sama secara otomatis dibuat).
- "Menyebarkan" catatan yang diketik ke dalam kolom menggunakan
(...).*
.
kamus json
Tetapi trik seperti itu, seperti yang telah kami terapkan di atas, tidak akan berfungsi jika tidak ada tipe tabel yang sesuai untuk membuat "unfastening". Situasi yang sama persis akan muncul, dan jika sebagai sumber data untuk serialisasi kami mencoba menggunakan
baris CTE, dan bukan tabel "nyata" .
Dalam hal ini,
fungsi untuk bekerja dengan json akan membantu kami:
... , p AS (
Perlu dicatat bahwa ketika menggambarkan struktur target, kita tidak bisa mendaftar semua bidang string sumber, tetapi hanya yang benar-benar kita butuhkan. Jika kita memiliki tabel "asli", maka lebih baik menggunakan fungsi
json_populate_record
.
Kami masih memiliki akses ke kamus sekali, tetapi
biaya serialisasi json [cukup] cukup tinggi , jadi masuk akal untuk menggunakan metode ini hanya dalam beberapa kasus ketika CTE Scan "jujur" menunjukkan dirinya lebih buruk.
Menguji kinerja
Jadi, kami punya dua cara untuk membuat serialisasi data ke dalam kamus -
hstore / json_object . Selain itu, array kunci dan nilai juga dapat dihasilkan dalam dua cara, dengan konversi internal atau eksternal ke teks:
array_agg (i :: text) / array_agg (i) :: text [] .
Mari kita periksa keefektifan berbagai jenis serialisasi menggunakan contoh sintetis murni - kita membuat
serialisasi sejumlah kunci yang berbeda :
WITH dict AS ( SELECT hstore( array_agg(i::text) , array_agg(i::text) ) FROM generate_series(1, ...) i ) TABLE dict;
Skrip evaluasi: serialisasi WITH T AS ( SELECT * , ( SELECT regexp_replace(ea[array_length(ea, 1)], '^Execution Time: (\d+\.\d+) ms$', '\1')::real et FROM ( SELECT array_agg(el) ea FROM dblink('port= ' || current_setting('port') || ' dbname=' || current_database(), $$ explain analyze WITH dict AS ( SELECT hstore( array_agg(i::text) , array_agg(i::text) ) FROM generate_series(1, $$ || (1 << v) || $$) i ) TABLE dict $$) T(el text) ) T ) et FROM generate_series(0, 19) v , LATERAL generate_series(1, 7) i ORDER BY 1, 2 ) SELECT v , avg(et)::numeric(32,3) FROM T GROUP BY 1 ORDER BY 1;

Pada PostgreSQL 11, hingga sekitar ukuran kamus 2 ^ 12 kunci,
serialisasi dalam json membutuhkan waktu lebih sedikit . Kombinasi json_object dan konversi tipe "internal" dari
array_agg(i::text)
adalah yang paling efisien.
Sekarang mari kita coba membaca nilai dari setiap tombol 8 kali - karena jika Anda tidak mengakses kamus, lalu mengapa itu diperlukan?
Skrip evaluasi: membaca dari kamus WITH T AS ( SELECT * , ( SELECT regexp_replace(ea[array_length(ea, 1)], '^Execution Time: (\d+\.\d+) ms$', '\1')::real et FROM ( SELECT array_agg(el) ea FROM dblink('port= ' || current_setting('port') || ' dbname=' || current_database(), $$ explain analyze WITH dict AS ( SELECT json_object( array_agg(i::text) , array_agg(i::text) ) FROM generate_series(1, $$ || (1 << v) || $$) i ) SELECT (TABLE dict) -> (i % ($$ || (1 << v) || $$) + 1)::text FROM generate_series(1, $$ || (1 << (v + 3)) || $$) i $$) T(el text) ) T ) et FROM generate_series(0, 19) v , LATERAL generate_series(1, 7) i ORDER BY 1, 2 ) SELECT v , avg(et)::numeric(32,3) FROM T GROUP BY 1 ORDER BY 1;

Dan ... sudah sekitar
2 ^ 6 kunci, membaca dari kamus json mulai kehilangan untuk membaca dari hstore beberapa kali, untuk jsonb hal yang sama terjadi pada 2 ^ 9.
Kesimpulan akhir:
- jika Anda perlu BERGABUNG dengan catatan berulang kali - lebih baik menggunakan "tabel matching"
- jika kamus Anda diharapkan kecil dan Anda akan membaca sedikit darinya - Anda dapat menggunakan json [b]
- dalam semua kasus lain, hstore + array_agg (i :: text) akan lebih efisien