สวัสดีทุกคน! ฉันเป็นนักพัฒนาแบ็กเอนด์ที่เขียนไมโครเซอร์วิสใน Java + Spring ฉันทำงานในทีมพัฒนาผลิตภัณฑ์ภายในทีมหนึ่งของ Tinkoff
ในทีมของเรา คำถามเกี่ยวกับการเพิ่มประสิทธิภาพการสืบค้นใน DBMS มักเกิดขึ้น คุณมักจะต้องการที่จะเร็วขึ้นอีกเล็กน้อย แต่คุณไม่สามารถทำได้เสมอไปด้วยดัชนีที่สร้างขึ้นอย่างพิถีพิถัน—คุณต้องมองหาวิธีแก้ปัญหาบางอย่าง ระหว่างการท่องเว็บเพื่อค้นหาการเพิ่มประสิทธิภาพที่เหมาะสมเมื่อทำงานกับฐานข้อมูล ฉันพบว่า
ฉันต้องการแปลบทความสั้น ๆ โดย Marcus ให้คุณ สามารถเรียกได้ว่าเป็นแถลงการณ์ที่พยายามดึงดูดความสนใจไปยังปัญหาเก่า แต่ยังคงเกี่ยวข้องกับปัญหาประสิทธิภาพของการดำเนินการออฟเซ็ตตามมาตรฐาน SQL
ในบางสถานที่ฉันจะเสริมผู้เขียนด้วยคำอธิบายและความคิดเห็น ฉันจะเรียกสถานที่ดังกล่าวทั้งหมดว่า “ประมาณ” เพื่อความชัดเจนยิ่งขึ้น
บทนำเล็กน้อย
ฉันคิดว่าหลายคนรู้ว่าการทำงานกับการเลือกเพจผ่านออฟเซ็ตนั้นมีปัญหาและช้าเพียงใด คุณรู้ไหมว่าสามารถแทนที่ด้วยการออกแบบที่มีประสิทธิภาพมากขึ้นได้อย่างง่ายดาย
ดังนั้นคีย์เวิร์ด offset จะบอกฐานข้อมูลให้ข้าม n เรคคอร์ดแรกในคำขอ อย่างไรก็ตาม ฐานข้อมูลยังคงจำเป็นต้องอ่าน n บันทึกแรกเหล่านี้จากดิสก์ ตามลำดับที่กำหนด (หมายเหตุ: ใช้การเรียงลำดับหากมีการระบุ) และเมื่อนั้นเท่านั้นจึงจะสามารถส่งคืนบันทึกตั้งแต่ n+1 เป็นต้นไป สิ่งที่น่าสนใจที่สุดคือปัญหาไม่ได้อยู่ในการใช้งานเฉพาะใน DBMS แต่อยู่ในคำจำกัดความดั้งเดิมตามมาตรฐาน:
...แถวแรกจะถูกจัดเรียงตาม จากนั้นจำกัดโดยการลดจำนวนแถวที่ระบุใน จากจุดเริ่มต้น...
-SQL:2016 ส่วนที่ 2 4.15.3 ตารางที่ได้รับมา (หมายเหตุ: ปัจจุบันเป็นมาตรฐานที่ใช้มากที่สุด)
จุดสำคัญที่นี่คือออฟเซ็ตใช้พารามิเตอร์ตัวเดียว - จำนวนบันทึกที่จะข้าม ก็แค่นั้นแหละ ตามคำจำกัดความนี้ DBMS จะสามารถดึงข้อมูลบันทึกทั้งหมดเท่านั้น จากนั้นจึงละทิ้งบันทึกที่ไม่จำเป็นออกไป แน่นอนว่าคำจำกัดความของการชดเชยนี้บังคับให้เราทำงานพิเศษ และไม่สำคัญว่าจะเป็น SQL หรือ NoSQL ก็ตาม
เจ็บอีกนิดหน่อยเท่านั้น
ปัญหาเกี่ยวกับการชดเชยไม่ได้จบเพียงแค่นั้น และนี่คือเหตุผล หากระหว่างการอ่านข้อมูลสองหน้าจากดิสก์ หากการดำเนินการอื่นแทรกบันทึกใหม่ จะเกิดอะไรขึ้นในกรณีนี้
เมื่อใช้ออฟเซ็ตเพื่อข้ามบันทึกจากหน้าก่อนหน้า ในสถานการณ์ของการเพิ่มบันทึกใหม่ระหว่างการอ่านหน้าต่างๆ คุณมักจะได้รับการซ้ำกัน (หมายเหตุ: สิ่งนี้เป็นไปได้เมื่อเราอ่านทีละหน้าโดยใช้ลำดับตามโครงสร้าง จากนั้น ในช่วงกลางของผลลัพธ์ของเรา มันอาจได้รายการใหม่)
ตัวเลขนี้แสดงให้เห็นสถานการณ์นี้อย่างชัดเจน ฐานจะอ่าน 10 ระเบียนแรก หลังจากนั้นจึงแทรกระเบียนใหม่ ซึ่งจะชดเชยระเบียนที่อ่านทั้งหมดด้วย 1 จากนั้นฐานจะใช้หน้าใหม่จาก 10 ระเบียนถัดไป และไม่เริ่มต้นจากวันที่ 11 เท่าที่ควร แต่จาก วันที่ 10 ทำซ้ำบันทึกนี้ มีความผิดปกติอื่น ๆ ที่เกี่ยวข้องกับการใช้สำนวนนี้ แต่นี่เป็นอาการที่พบบ่อยที่สุด
ดังที่เราได้ทราบไปแล้ว สิ่งเหล่านี้ไม่ใช่ปัญหาของ DBMS เฉพาะหรือการนำไปใช้งาน ปัญหาอยู่ในการกำหนดการแบ่งหน้าตามมาตรฐาน SQL เราบอก DBMS ว่าควรดึงข้อมูลหน้าใดหรือต้องข้ามบันทึกกี่รายการ ฐานข้อมูลไม่สามารถเพิ่มประสิทธิภาพคำขอดังกล่าวได้ เนื่องจากมีข้อมูลน้อยเกินไปสำหรับสิ่งนี้
นอกจากนี้ ควรชี้แจงด้วยว่านี่ไม่ใช่ปัญหากับคำหลักบางคำ แต่เป็นปัญหาเกี่ยวกับความหมายของข้อความค้นหา มีไวยากรณ์อีกหลายรูปแบบที่เหมือนกันในลักษณะที่เป็นปัญหา:
- คำสำคัญชดเชยเป็นไปตามที่กล่าวไว้ก่อนหน้านี้
- การสร้างขีดจำกัดของคำหลักสองคำ [offset] (แม้ว่าตัวจำกัดเองก็ไม่ได้แย่ขนาดนั้น)
- การกรองตามขอบเขตล่าง ตามการกำหนดหมายเลขแถว (เช่น row_number(), rownum ฯลฯ)
สำนวนทั้งหมดนี้เพียงบอกคุณว่าต้องข้ามกี่บรรทัด โดยไม่มีข้อมูลเพิ่มเติมหรือบริบท
ต่อมาในบทความนี้ คำสำคัญ offset จะถูกใช้เป็นบทสรุปของตัวเลือกเหล่านี้ทั้งหมด
ชีวิตที่ปราศจากออฟเซ็ต
ทีนี้ลองจินตนาการว่าโลกของเราจะเป็นอย่างไรหากปราศจากปัญหาเหล่านี้ ปรากฎว่าชีวิตที่ไม่มีการชดเชยนั้นไม่ใช่เรื่องยาก: ด้วยการเลือก คุณสามารถเลือกเฉพาะแถวที่เรายังไม่ได้เห็น (หมายเหตุ: นั่นคือแถวที่ไม่ได้อยู่ในหน้าก่อนหน้า) โดยใช้เงื่อนไขโดยที่
ในกรณีนี้ เราเริ่มต้นจากข้อเท็จจริงที่ว่าการเลือกถูกดำเนินการบนชุดที่ได้รับคำสั่ง (ลำดับเก่าที่ดีโดย) เนื่องจากเรามีชุดคำสั่ง เราจึงสามารถใช้ตัวกรองที่ค่อนข้างง่ายเพื่อรับเฉพาะข้อมูลที่อยู่เบื้องหลังบันทึกล่าสุดของหน้าก่อนหน้า:
SELECT ...
FROM ...
WHERE ...
AND id < ?last_seen_id
ORDER BY id DESC
FETCH FIRST 10 ROWS ONLY
นั่นคือหลักการทั้งหมดของแนวทางนี้ แน่นอนว่าสิ่งต่างๆ จะสนุกยิ่งขึ้นเมื่อจัดเรียงตามคอลัมน์ต่างๆ แต่แนวคิดยังคงเหมือนเดิม สิ่งสำคัญคือต้องทราบว่าการออกแบบนี้ใช้ได้กับหลาย ๆ คน
วิธีการนี้เรียกว่าวิธีการค้นหาหรือการแบ่งหน้าชุดคีย์ มันแก้ปัญหาผลลัพธ์แบบลอยตัว (หมายเหตุ: สถานการณ์ที่มีการเขียนระหว่างการอ่านหน้าตามที่อธิบายไว้ก่อนหน้านี้) และแน่นอนว่าสิ่งที่เราทุกคนชื่นชอบ มันทำงานได้เร็วกว่าและเสถียรกว่าออฟเซ็ตแบบคลาสสิก ความเสถียรอยู่ที่ความจริงที่ว่าเวลาประมวลผลคำขอไม่เพิ่มขึ้นตามสัดส่วนของจำนวนตารางที่ร้องขอ (หมายเหตุ: หากคุณต้องการเรียนรู้เพิ่มเติมเกี่ยวกับการทำงานของวิธีต่าง ๆ ในการแบ่งหน้าคุณสามารถทำได้
หนึ่งในสไลด์
แล้วเครื่องมือล่ะ?
การแบ่งหน้าบนคีย์มักไม่เหมาะสมเนื่องจากขาดเครื่องมือสนับสนุนสำหรับวิธีนี้ เครื่องมือพัฒนาส่วนใหญ่ รวมถึงเฟรมเวิร์กต่างๆ ไม่อนุญาตให้คุณเลือกวิธีดำเนินการแบ่งหน้าอย่างชัดเจน
สถานการณ์เลวร้ายลงจากความจริงที่ว่าวิธีการที่อธิบายไว้นั้นต้องการการสนับสนุนแบบ end-to-end ในเทคโนโลยีที่ใช้ตั้งแต่ DBMS ไปจนถึงการดำเนินการคำขอ AJAX ในเบราว์เซอร์ที่มีการเลื่อนอย่างไม่มีที่สิ้นสุด แทนที่จะระบุเพียงหมายเลขหน้า ตอนนี้คุณต้องระบุชุดคีย์สำหรับเพจทั้งหมดพร้อมกัน
อย่างไรก็ตาม จำนวนเฟรมเวิร์กที่รองรับการแบ่งหน้าบนคีย์กำลังค่อยๆ เพิ่มขึ้น นี่คือสิ่งที่เรามีในขณะนี้:
jooq สำหรับจาวา;order_query สำหรับทับทิม;ก้อน иการแบ่งหน้าการเลื่อน Django Infinite สำหรับจังโก้;SQL Alchemy ชุดคีย์คีย์ SQL สำหรับหลาม;เปลวไฟคงอยู่ — เกณฑ์ API สำหรับการใช้งาน JPADBIx::Class::Wrapper สำหรับภาษาเพิร์ล;Massive.js ผู้ทำแผนที่สำหรับ Node.jsเอกสารชุดคีย์ .
(หมายเหตุ: ลิงก์บางส่วนถูกลบออกเนื่องจากในขณะที่แปลห้องสมุดบางแห่งไม่ได้รับการอัปเดตตั้งแต่ปี 2017-2018 หากคุณสนใจสามารถดูแหล่งที่มาดั้งเดิมได้)
ในขณะนี้คุณต้องการความช่วยเหลือจากคุณ หากคุณพัฒนาหรือสนับสนุนเฟรมเวิร์กที่ใช้การแบ่งหน้า ฉันขอวิงวอนให้คุณให้การสนับสนุนดั้งเดิมสำหรับการแบ่งหน้าบนคีย์ หากคุณมีคำถามหรือต้องการความช่วยเหลือ เรายินดีที่จะช่วยเหลือ (
หากคุณใช้โซลูชันสำเร็จรูปที่คุณคิดว่าสมควรได้รับการสนับสนุนสำหรับการแบ่งหน้าด้วยคีย์ ให้สร้างคำขอ หรือแม้แต่เสนอโซลูชันสำเร็จรูป หากเป็นไปได้ คุณสามารถเชื่อมโยงไปยังบทความนี้ได้
ข้อสรุป
เหตุผลที่วิธีการที่ง่ายและมีประโยชน์เช่นการแบ่งหน้าด้วยคีย์นั้นไม่แพร่หลายนั้นไม่ใช่ว่าจะนำไปใช้ในทางเทคนิคได้ยากหรือต้องใช้ความพยายามอย่างมาก เหตุผลหลักก็คือ หลายคนคุ้นเคยกับการมองเห็นและทำงานกับออฟเซ็ต - แนวทางนี้ถูกกำหนดโดยมาตรฐานเอง
เป็นผลให้มีเพียงไม่กี่คนที่คิดเกี่ยวกับการเปลี่ยนวิธีการแบ่งหน้า และด้วยเหตุนี้ การสนับสนุนเครื่องมือจากเฟรมเวิร์กและไลบรารีจึงพัฒนาได้ไม่ดี ดังนั้น หากแนวคิดและเป้าหมายของการแบ่งหน้าแบบไร้ออฟเซ็ตอยู่ใกล้คุณ ช่วยกระจายมันออกไป!
ที่มา:
ผู้เขียน : มาร์คุส วินันด์
ที่มา: will.com