ทีแอล; DR: JSONB ช่วยให้การพัฒนาสคีมาฐานข้อมูลง่ายขึ้นอย่างมาก โดยไม่กระทบต่อประสิทธิภาพการสืบค้น
การแนะนำ
เราจะยกตัวอย่างคลาสสิกของกรณีการใช้งานที่เก่าแก่ที่สุดแห่งหนึ่งในโลกของฐานข้อมูลเชิงสัมพันธ์ (ฐานข้อมูล) เรามีเอนทิตี และเราจำเป็นต้องบันทึกคุณสมบัติบางอย่าง (แอตทริบิวต์) ของเอนทิตีนี้ แต่ไม่ใช่ทุกอินสแตนซ์อาจมีชุดคุณสมบัติที่เหมือนกัน และอาจมีการเพิ่มคุณสมบัติเพิ่มเติมในอนาคต
วิธีที่ง่ายที่สุดในการแก้ปัญหานี้คือการสร้างคอลัมน์ในตารางฐานข้อมูลสำหรับแต่ละค่าคุณสมบัติ และเพียงกรอกข้อมูลที่จำเป็นสำหรับอินสแตนซ์เอนทิตีเฉพาะ ยอดเยี่ยม! แก้ไขปัญหา... จนกว่าตารางของคุณจะมีบันทึกนับล้านรายการ และคุณต้องเพิ่มบันทึกใหม่
พิจารณารูปแบบ EAV (
อย่างไรก็ตาม ฉันจะไม่เขียนโพสต์นี้หากไม่มีข้อเสียของแนวทาง EVA ตัวอย่างเช่นหากต้องการรับเอนทิตีตั้งแต่หนึ่งรายการขึ้นไปซึ่งมี 1 แอตทริบิวต์ในแต่ละแบบสอบถามจำเป็นต้องมี 2 การรวมในแบบสอบถาม: อันแรกคือการรวมกับตารางแอตทริบิวต์ส่วนที่สองคือการรวมกับตารางค่า หากเอนทิตีมี 2 คุณลักษณะ ก็จำเป็นต้องมีการรวม 4 รายการ! นอกจากนี้ โดยทั่วไปแล้วแอ็ตทริบิวต์ทั้งหมดจะถูกจัดเก็บเป็นสตริง ซึ่งส่งผลให้เกิดการพิมพ์แบบหล่อสำหรับทั้งผลลัพธ์และส่วนคำสั่ง WHERE หากคุณเขียนแบบสอบถามจำนวนมาก ถือว่าสิ้นเปลืองทรัพยากรอย่างมาก
แม้จะมีข้อบกพร่องที่ชัดเจนเหล่านี้ แต่ EAV ก็ถูกนำมาใช้เพื่อแก้ไขปัญหาประเภทนี้มานานแล้ว สิ่งเหล่านี้เป็นข้อบกพร่องที่หลีกเลี่ยงไม่ได้ และไม่มีทางเลือกอื่นที่ดีกว่านี้แล้ว
แต่แล้ว “เทคโนโลยี” ใหม่ก็ปรากฏขึ้นใน PostgreSQL...
ตั้งแต่ PostgreSQL 9.4 เป็นต้นไป ประเภทข้อมูล JSONB จะถูกเพิ่มเพื่อจัดเก็บข้อมูลไบนารี่ของ JSON แม้ว่าโดยทั่วไปการจัดเก็บ JSON ในรูปแบบนี้จะใช้พื้นที่และเวลามากกว่า JSON แบบข้อความธรรมดาเล็กน้อย แต่การดำเนินการกับ JSON นั้นเร็วกว่ามาก JSONB ยังรองรับการจัดทำดัชนี ซึ่งทำให้การสืบค้นเร็วยิ่งขึ้นไปอีก
ชนิดข้อมูล JSONB ช่วยให้เราสามารถแทนที่รูปแบบ EAV ที่ยุ่งยากได้โดยการเพิ่มคอลัมน์ JSONB เพียงคอลัมน์เดียวลงในตารางเอนทิตีของเรา ซึ่งทำให้การออกแบบฐานข้อมูลง่ายขึ้นมาก แต่หลายคนโต้แย้งว่าสิ่งนี้ควรมาพร้อมกับประสิทธิภาพการทำงานที่ลดลง... นั่นคือเหตุผลที่ฉันเขียนบทความนี้
การตั้งค่าฐานข้อมูลทดสอบ
สำหรับการเปรียบเทียบนี้ ฉันได้สร้างฐานข้อมูลบนการติดตั้ง PostgreSQL 9.5 ใหม่บนบิลด์ 80 ดอลลาร์
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
);
ด้านล่างนี้เป็นตารางที่จะจัดเก็บข้อมูลเดียวกัน แต่มีแอตทริบิวต์ในคอลัมน์ประเภท JSONB – คุณสมบัติ.
CREATE TABLE entity_jsonb (
id SERIAL PRIMARY KEY,
name TEXT,
description TEXT,
properties JSONB
);
ดูง่ายกว่ามากใช่ไหม? จากนั้นมันถูกเพิ่มลงในตารางเอนทิตี (เอกลักษณ์ & Entity_jsonb) 10 ล้านบันทึก ดังนั้นตารางจึงเต็มไปด้วยข้อมูลเดียวกันโดยใช้รูปแบบ EAV และวิธีการด้วยคอลัมน์ JSONB - Entity_jsonb.คุณสมบัติ. ดังนั้นเราจึงได้รับข้อมูลหลายประเภทจากชุดคุณสมบัติทั้งหมด ข้อมูลตัวอย่าง:
{
id: 1
name: "Entity1"
description: "Test entity no. 1"
properties: {
color: "red"
lenght: 120
width: 3.1882420
hassomething: true
country: "Belgium"
}
}
ตอนนี้เรามีข้อมูลเดียวกันสำหรับทั้งสองตัวเลือก มาเริ่มเปรียบเทียบการใช้งานในที่ทำงานกันดีกว่า!
ลดความซับซ้อนของการออกแบบของคุณ
ก่อนหน้านี้มีการระบุไว้ว่าการออกแบบฐานข้อมูลนั้นง่ายขึ้นอย่างมาก: หนึ่งตารางโดยใช้คอลัมน์ JSONB สำหรับคุณสมบัติ แทนที่จะใช้สามตารางสำหรับ EAV แต่สิ่งนี้สะท้อนให้เห็นในคำขออย่างไร การอัปเดตคุณสมบัติเอนทิตีหนึ่งมีลักษณะดังนี้:
-- 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;
อย่างที่คุณเห็นคำขอสุดท้ายไม่ได้ดูง่ายกว่านี้ ในการอัปเดตค่าของคุณสมบัติในวัตถุ JSONB เราต้องใช้ฟังก์ชัน
ตอนนี้ เรามาเลือกเอนทิตีที่เราเพิ่งอัปเดตตามสีใหม่:
-- 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';
ฉันคิดว่าเราสามารถตกลงกันว่าอันที่สองนั้นสั้นกว่า (ไม่ต้องเข้าร่วม!) ดังนั้นจึงอ่านง่ายกว่า JSONB ชนะที่นี่! เราใช้ตัวดำเนินการ JSON ->> เพื่อรับสีเป็นค่าข้อความจากวัตถุ JSONB ยังมีวิธีที่สองเพื่อให้ได้ผลลัพธ์เดียวกันในโมเดล JSONB โดยใช้ตัวดำเนินการ @>:
-- JSONB
SELECT name
FROM entity_jsonb
WHERE properties @> '{"color": "blue"}';
สิ่งนี้ซับซ้อนกว่าเล็กน้อย: เราจะตรวจสอบเพื่อดูว่าออบเจ็กต์ JSON ในคอลัมน์คุณสมบัติมีออบเจ็กต์ที่อยู่ทางด้านขวาของตัวดำเนินการ @> หรือไม่ อ่านน้อยลง มีประสิทธิภาพมากขึ้น (ดูด้านล่าง)
มาทำให้การใช้ JSONB ง่ายยิ่งขึ้นเมื่อคุณต้องการเลือกคุณสมบัติหลายรายการพร้อมกัน นี่คือที่มาของแนวทาง JSONB: เราเพียงเลือกคุณสมบัติเป็นคอลัมน์เพิ่มเติมในชุดผลลัพธ์ของเราโดยไม่จำเป็นต้องรวม:
-- JSONB
SELECT name
, properties ->> 'color'
, properties ->> 'country'
FROM entity_jsonb
WHERE id = 120;
ด้วย EAV คุณจะต้องมี 2 การรวมสำหรับแต่ละคุณสมบัติที่คุณต้องการสืบค้น ในความคิดของฉัน ข้อความค้นหาข้างต้นแสดงให้เห็นถึงความเรียบง่ายอย่างมากในการออกแบบฐานข้อมูล ดูตัวอย่างเพิ่มเติมของวิธีเขียนคำสั่ง JSONB ได้ที่
ตอนนี้ได้เวลาพูดคุยเกี่ยวกับประสิทธิภาพแล้ว
การปฏิบัติ
เพื่อเปรียบเทียบประสิทธิภาพที่ผมใช้
การอัปเดตข้อมูลแสดงผลลัพธ์ต่อไปนี้ในแง่ของเวลา (เป็นมิลลิวินาที) โปรดทราบว่ามาตราส่วนเป็นลอการิทึม:
เราจะเห็นว่า JSONB นั้นเร็วกว่า EAV มาก (> 50000-x) หากคุณไม่ได้ใช้ดัชนี ด้วยเหตุผลที่ระบุไว้ข้างต้น เมื่อเราจัดทำดัชนีคอลัมน์ด้วยคีย์หลัก ความแตกต่างเกือบจะหายไป แต่ JSONB ยังคงเร็วกว่า EAV ถึง 1,3 เท่า โปรดทราบว่าดัชนีในคอลัมน์ JSONB ไม่มีผลกระทบที่นี่ เนื่องจากเราไม่ได้ใช้คอลัมน์คุณสมบัติในเกณฑ์การประเมิน
สำหรับการเลือกข้อมูลตามมูลค่าทรัพย์สิน เราจะได้ผลลัพธ์ดังต่อไปนี้ (มาตราส่วนปกติ):
คุณจะสังเกตได้ว่า JSONB ทำงานได้เร็วกว่า EAV อีกครั้งโดยไม่มีดัชนี แต่เมื่อ EAV ที่มีดัชนี ก็จะยังคงทำงานได้เร็วกว่า JSONB แต่แล้วฉันก็เห็นว่าเวลาสำหรับการสืบค้น JSONB นั้นเท่ากัน สิ่งนี้ทำให้ฉันทราบว่าดัชนี GIN ไม่ทำงาน เห็นได้ชัดว่าเมื่อคุณใช้ดัชนี GIN ในคอลัมน์ที่มีคุณสมบัติที่มีการเติมข้อมูล จะมีผลเฉพาะเมื่อใช้ตัวดำเนินการรวม @> เท่านั้น ฉันใช้สิ่งนี้ในการทดสอบใหม่และมีผลกระทบอย่างมากต่อเวลา: เพียง 0,153 มิลลิวินาที! ซึ่งเร็วกว่า EAV ถึง 15000 เท่า และเร็วกว่าตัวดำเนินการ ->> ถึง 25000 เท่า
ฉันคิดว่ามันเร็วพอ!
ขนาดตารางฐานข้อมูล
ลองเปรียบเทียบขนาดตารางสำหรับทั้งสองวิธี ใน psql เราสามารถแสดงขนาดของตารางและดัชนีทั้งหมดได้โดยใช้คำสั่ง ดีทีไอ+
สำหรับแนวทาง EAV ขนาดตารางจะอยู่ที่ประมาณ 3068 MB และจัดทำดัชนีได้สูงสุด 3427 MB รวมเป็น 6,43 GB วิธี JSONB ใช้ 1817 MB สำหรับตาราง และ 318 MB สำหรับดัชนี ซึ่งก็คือ 2,08 GB น้อยลง 3 เท่า! ข้อเท็จจริงนี้ทำให้ฉันประหลาดใจเล็กน้อยเนื่องจากเราจัดเก็บชื่อคุณสมบัติไว้ในออบเจ็กต์ JSONB ทุกอัน
แต่ถึงกระนั้น ตัวเลขก็พูดได้ด้วยตัวเอง: ใน EAV เราเก็บคีย์ต่างประเทศจำนวนเต็ม 2 อันต่อค่าแอตทริบิวต์ ส่งผลให้มีข้อมูลเพิ่มเติม 8 ไบต์ นอกจากนี้ EAV ยังเก็บค่าคุณสมบัติทั้งหมดเป็นข้อความ ในขณะที่ JSONB จะใช้ค่าตัวเลขและค่าบูลีนภายในเมื่อเป็นไปได้ ส่งผลให้มีขนาดเล็กลง
ผลของการ
โดยรวมแล้ว ฉันคิดว่าการบันทึกคุณสมบัติเอนทิตีในรูปแบบ JSONB ช่วยให้การออกแบบและบำรุงรักษาฐานข้อมูลของคุณง่ายขึ้นมาก หากคุณกำลังเรียกใช้แบบสอบถามจำนวนมาก การเก็บทุกอย่างไว้ในตารางเดียวกันเนื่องจากเอนทิตีจะทำงานได้อย่างมีประสิทธิภาพมากขึ้น และความจริงที่ว่าสิ่งนี้ทำให้การโต้ตอบระหว่างข้อมูลง่ายขึ้นนั้นเป็นข้อดีอยู่แล้ว แต่ฐานข้อมูลผลลัพธ์จะมีปริมาณน้อยกว่า 3 เท่า
นอกจากนี้ จากการทดสอบที่ดำเนินการ เราสามารถสรุปได้ว่าการสูญเสียประสิทธิภาพไม่มีนัยสำคัญมาก ในบางกรณี JSONB ยังเร็วกว่า EAV อีกด้วย ทำให้ดียิ่งขึ้นไปอีก อย่างไรก็ตาม เกณฑ์มาตรฐานนี้ไม่ครอบคลุมทุกด้าน (เช่น เอนทิตีที่มีคุณสมบัติจำนวนมาก การเพิ่มขึ้นอย่างมากในจำนวนคุณสมบัติของข้อมูลที่มีอยู่...) ดังนั้นหากคุณมีข้อเสนอแนะใดๆ เกี่ยวกับวิธีการปรับปรุงคุณสมบัติเหล่านั้น โปรดอย่าลังเลที่จะแสดงความคิดเห็น!
ที่มา: will.com