TL; DR: JSONB boleh memudahkan pembangunan skema pangkalan data tanpa mengorbankan prestasi pertanyaan.
Pengenalan
Mari kita berikan contoh klasik mungkin salah satu kes penggunaan tertua dalam dunia pangkalan data hubungan (pangkalan data): kita mempunyai entiti, dan kita perlu menyimpan sifat tertentu (atribut) entiti ini. Tetapi tidak semua keadaan mungkin mempunyai set sifat yang sama dan lebih banyak sifat boleh ditambahkan pada masa hadapan.
Cara paling mudah untuk menyelesaikan masalah ini ialah membuat lajur dalam jadual pangkalan data untuk setiap nilai harta, dan cukup isikan nilai yang diperlukan untuk contoh entiti tertentu. Hebat! Masalah selesai... sehingga jadual anda mengandungi berjuta-juta rekod dan anda perlu menambah rekod baharu.
Pertimbangkan corak EAV (), ia berlaku agak kerap. Satu jadual mengandungi entiti (rekod), jadual lain mengandungi nama sifat (atribut), dan jadual ketiga mengaitkan entiti dengan atributnya dan mengandungi nilai atribut tersebut untuk entiti semasa. Ini memberi anda keupayaan untuk mempunyai set sifat yang berbeza untuk objek yang berbeza, dan juga menambah sifat dengan cepat tanpa mengubah struktur pangkalan data.
Walau bagaimanapun, saya tidak akan menulis siaran ini jika tiada kelemahan pada pendekatan EVA. Jadi, sebagai contoh, untuk mendapatkan satu atau lebih entiti yang mempunyai 1 atribut setiap satu, 2 gabungan diperlukan dalam pertanyaan: yang pertama ialah gabungan dengan jadual atribut, yang kedua ialah gabungan dengan jadual nilai. Jika entiti mempunyai 2 atribut, maka 4 gabungan diperlukan! Selain itu, semua atribut biasanya disimpan sebagai rentetan, yang menghasilkan penghantaran jenis untuk kedua-dua keputusan dan klausa WHERE. Jika anda menulis banyak pertanyaan, maka ini agak membazir dari segi penggunaan sumber.
Walaupun terdapat kelemahan yang jelas ini, EAV telah lama digunakan untuk menyelesaikan jenis masalah ini. Ini adalah kelemahan yang tidak dapat dielakkan, dan tidak ada alternatif yang lebih baik.
Tetapi kemudian "teknologi" baru muncul dalam PostgreSQL...
Bermula dengan PostgreSQL 9.4, jenis data JSONB telah ditambahkan untuk menyimpan data binari JSON. Walaupun menyimpan JSON dalam format ini biasanya mengambil lebih sedikit ruang dan masa berbanding JSON teks biasa, melaksanakan operasi padanya adalah lebih pantas. JSONB juga menyokong pengindeksan, yang menjadikan pertanyaan lebih pantas.
Jenis data JSONB membolehkan kami menggantikan corak EAV yang menyusahkan dengan menambahkan hanya satu lajur JSONB pada jadual entiti kami, dengan sangat memudahkan reka bentuk pangkalan data. Tetapi ramai yang berpendapat bahawa ini harus disertai dengan penurunan produktiviti ... Itulah sebabnya saya menulis artikel ini.
Menyediakan pangkalan data ujian
Untuk perbandingan ini, saya mencipta pangkalan data pada pemasangan baru PostgreSQL 9.5 pada binaan $80 Ubuntu 14.04 Selepas mengkonfigurasi beberapa parameter dalam postgresql.conf, saya menjalankan skrip menggunakan psql. Jadual berikut telah dibuat untuk membentangkan data dalam bentuk EAV:
CREATE TABLE entity (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT
);
CREATE TABLE entity_attribute (
id SERIAL PRIMARY KEY,
name TEXT
);
CREATE TABLE entity_attribute_value (
id SERIAL PRIMARY KEY,
entity_id INT REFERENCES entity(id),
entity_attribute_id INT REFERENCES entity_attribute(id),
value TEXT
);
Di bawah ialah jadual di mana data yang sama akan disimpan, tetapi dengan atribut dalam lajur jenis JSONB - hartanah.
CREATE TABLE entity_jsonb (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT,
properties JSONB
);
Nampak jauh lebih mudah, bukan? Kemudian ia ditambahkan pada jadual entiti (entiti & entity_jsonb) 10 juta rekod, dan oleh itu, jadual telah diisi dengan data yang sama menggunakan corak EAV dan pendekatan dengan lajur JSONB - entity_jsonb.properties. Oleh itu, kami menerima beberapa jenis data yang berbeza antara keseluruhan set sifat. Contoh data:
{
id: 1
name: "Entity1"
description: "Test entity no. 1"
properties: {
color: "red"
lenght: 120
width: 3.1882420
hassomething: true
country: "Belgium"
}
}Jadi sekarang kami mempunyai data yang sama untuk kedua-dua pilihan. Mari mulakan membandingkan pelaksanaan di tempat kerja!
Permudahkan reka bentuk anda
Sebelum ini telah dinyatakan bahawa reka bentuk pangkalan data sangat dipermudahkan: satu jadual, dengan menggunakan lajur JSONB untuk sifat, dan bukannya menggunakan tiga jadual untuk EAV. Tetapi bagaimana ini dicerminkan dalam permintaan? Mengemas kini satu sifat entiti kelihatan seperti ini:
-- EAV
UPDATE entity_attribute_value
SET value = 'blue'
WHERE entity_attribute_id = 1
AND entity_id = 120;
-- JSONB
UPDATE entity_jsonb
SET properties = jsonb_set(properties, '{"color"}', '"blue"')
WHERE id = 120;
Seperti yang anda lihat, permintaan terakhir tidak kelihatan lebih mudah. Untuk mengemas kini nilai harta dalam objek JSONB kita perlu menggunakan fungsi tersebut , dan harus melepasi nilai baharu kami sebagai objek JSONB. Walau bagaimanapun, kami tidak perlu mengetahui sebarang pengecam terlebih dahulu. Melihat contoh EAV, kita perlu mengetahui entity_id dan entity_attribute_id untuk melaksanakan kemas kini. Jika anda ingin mengemas kini harta dalam lajur JSONB berdasarkan nama objek, maka semuanya dilakukan dalam satu baris mudah.
Sekarang mari kita pilih entiti yang baru kita kemas kini berdasarkan warna baharunya:
-- EAV
SELECT e.name
FROM entity e
INNER JOIN entity_attribute_value eav ON e.id = eav.entity_id
INNER JOIN entity_attribute ea ON eav.entity_attribute_id = ea.id
WHERE ea.name = 'color' AND eav.value = 'blue';
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties ->> 'color' = 'blue';
Saya rasa kita boleh bersetuju bahawa yang kedua adalah lebih pendek (tidak menyertai!), dan oleh itu lebih mudah dibaca. JSONB menang di sini! Kami menggunakan operator JSON ->> untuk mendapatkan warna sebagai nilai teks daripada objek JSONB. Terdapat juga cara kedua untuk mencapai hasil yang sama dalam model JSONB menggunakan pengendali @>:
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties @> '{"color": "blue"}';
Ini sedikit lebih rumit: kami menyemak untuk melihat sama ada objek JSON dalam lajur sifatnya mengandungi objek yang berada di sebelah kanan pengendali @>. Kurang boleh dibaca, lebih produktif (lihat di bawah).
Mari permudahkan penggunaan JSONB apabila anda perlu memilih berbilang sifat sekaligus. Di sinilah pendekatan JSONB benar-benar masuk: kami hanya memilih sifat sebagai lajur tambahan dalam set hasil kami tanpa memerlukan gabungan:
-- JSONB
SELECT name
, properties ->> 'color'
, properties ->> 'country'
FROM entity_jsonb
WHERE id = 120;
Dengan EAV anda memerlukan 2 gabungan untuk setiap harta yang anda ingin tanya. Pada pendapat saya, pertanyaan di atas menunjukkan penyederhanaan yang hebat dalam reka bentuk pangkalan data. Lihat lebih banyak contoh cara menulis pertanyaan kepada JSONB, juga mungkin dalam jawatan.
Kini tiba masanya untuk bercakap tentang prestasi.
Produktiviti
Untuk membandingkan prestasi yang saya gunakan dalam pertanyaan, untuk mengira masa pelaksanaan. Setiap pertanyaan telah dilaksanakan sekurang-kurangnya tiga kali kerana perancang pertanyaan mengambil masa yang lebih lama untuk kali pertama. Mula-mula saya menjalankan pertanyaan tanpa sebarang indeks. Jelas sekali, ini adalah kelebihan JSONB, kerana gabungan yang diperlukan untuk EAV tidak boleh menggunakan indeks (medan kunci asing tidak diindeks). Selepas ini saya mencipta indeks pada 2 lajur kunci asing bagi jadual nilai EAV, serta indeks untuk lajur JSONB.
Kemas kini data menunjukkan keputusan berikut dari segi masa (dalam ms). Perhatikan bahawa skala adalah logaritma:

Kami melihat bahawa JSONB jauh (> 50000-x) lebih pantas daripada EAV jika anda tidak menggunakan indeks, atas sebab yang dinyatakan di atas. Apabila kami mengindeks lajur dengan kunci utama, perbezaan itu hampir hilang, tetapi JSONB masih 1,3 kali lebih pantas daripada EAV. Ambil perhatian bahawa indeks pada lajur JSONB tidak mempunyai kesan di sini kerana kami tidak menggunakan lajur sifat dalam kriteria penilaian.
Untuk memilih data berdasarkan nilai harta, kami mendapat keputusan berikut (skala normal):

Anda dapat melihat bahawa JSONB sekali lagi berfungsi lebih pantas daripada EAV tanpa indeks, tetapi apabila EAV dengan indeks, ia masih berfungsi lebih pantas daripada JSONB. Tetapi kemudian saya melihat bahawa masa untuk pertanyaan JSONB adalah sama, ini mendorong saya kepada fakta bahawa indeks GIN tidak berfungsi. Nampaknya apabila anda menggunakan indeks GIN pada lajur dengan sifat yang dihuni, ia hanya berkuat kuasa apabila menggunakan operator include @>. Saya menggunakan ini dalam ujian baharu dan ia mempunyai kesan yang besar pada masa itu: hanya 0,153ms! Ini adalah 15000 kali lebih pantas daripada EAV dan 25000 kali lebih pantas daripada operator ->>.
Saya rasa ia sudah cukup pantas!
Saiz jadual pangkalan data
Mari bandingkan saiz jadual untuk kedua-dua pendekatan. Dalam psql kita boleh menunjukkan saiz semua jadual dan indeks menggunakan arahan dti+

Untuk pendekatan EAV, saiz jadual adalah sekitar 3068 MB dan mengindeks sehingga 3427 MB untuk jumlah 6,43 GB. Pendekatan JSONB menggunakan 1817 MB untuk jadual dan 318 MB untuk indeks, iaitu 2,08 GB. Ternyata kurang 3 kali ganda! Fakta ini mengejutkan saya sedikit kerana kami menyimpan nama harta dalam setiap objek JSONB.
Namun begitu, nombor itu bercakap untuk diri mereka sendiri: dalam EAV kami menyimpan 2 kunci asing integer bagi setiap nilai atribut, menghasilkan 8 bait data tambahan. Selain itu, EAV menyimpan semua nilai harta sebagai teks, manakala JSONB akan menggunakan nilai angka dan boolean secara dalaman jika boleh, menghasilkan jejak yang lebih kecil.
Keputusan
Secara keseluruhannya, saya fikir menyimpan sifat entiti dalam format JSONB boleh menjadikan reka bentuk dan penyelenggaraan pangkalan data anda lebih mudah. Jika anda menjalankan banyak pertanyaan, maka menyimpan segala-galanya dalam jadual yang sama dengan entiti sebenarnya akan berfungsi dengan lebih cekap. Dan fakta bahawa ini memudahkan interaksi antara data sudah menjadi nilai tambah, tetapi pangkalan data yang dihasilkan adalah 3 kali lebih kecil dalam volum.
Selain itu, berdasarkan ujian yang dilakukan, kita boleh membuat kesimpulan bahawa kehilangan prestasi adalah sangat tidak penting. Dalam sesetengah kes, JSONB lebih pantas daripada EAV, menjadikannya lebih baik. Walau bagaimanapun, penanda aras ini sudah tentu tidak merangkumi semua aspek (cth. entiti dengan bilangan sifat yang sangat besar, peningkatan ketara dalam bilangan sifat data sedia ada,...), jadi jika anda mempunyai sebarang cadangan tentang cara untuk memperbaikinya , sila tinggalkan dalam komen!
Sumber: www.habr.com
