PlusMagi's Blog By Pitt Phunsanit Backend,Database PHP: การทำ Lazy Loading สำหรับความสัมพันธ์ของ Object

PHP: การทำ Lazy Loading สำหรับความสัมพันธ์ของ Object

ในโลกของการพัฒนาเว็บแอปพลิเคชันที่ใช้ Object-Relational Mapping (ORM) เช่น Doctrine หรือ Eloquent การทำงานกับข้อมูลที่มีความสัมพันธ์กัน (Relationships) เป็นเรื่องปกติเสมอ เรามักจะต้องดึงข้อมูลหลายส่วนมาประกอบกัน เช่น การดึงข้อมูล ‘บทความ’ พร้อมด้วย ‘ผู้เขียน’ และ ‘ความคิดเห็น’ ของบทความนั้นๆ

อย่างไรก็ตาม หากเราไม่ระมัดระวังในการเรียกใช้ข้อมูล ความสัมพันธ์เหล่านี้จะนำไปสู่ปัญหาด้านประสิทธิภาพที่ร้ายแรงที่เรียกว่า N+1 Problem ซึ่งเป็นสาเหตุหลักที่ทำให้แอปพลิเคชันทำงานช้าและเกิด Latency สูง

💡 N+1 Problem คืออะไร?

ปัญหานี้เกิดขึ้นเมื่อระบบทำการ Query ข้อมูลในฐานข้อมูลซ้ำๆ มากเกินความจำเป็น ตัวอย่างเช่น หากเราต้องการดึงรายการบทความ 10 บทความ (Query ครั้งที่ 1) และสำหรับแต่ละบทความ ระบบต้อง Query ข้อมูลผู้เขียน (Query ครั้งที่ 2 ถึง 11) และความคิดเห็น (Query ครั้งที่ 12 ถึง 21) ทำให้จำนวน Query ทั้งหมดพุ่งสูงถึง $1 + (N imes 2)$ ครั้ง ซึ่งเป็นภาระงานที่ไม่จำเป็นต่อ PostgreSQL และทำให้ประสิทธิภาพโดยรวมลดลงอย่างมาก

🚀 Lazy Loading คือทางออกอย่างไร?

Lazy Loading หรือการโหลดแบบขี้เกียจ คือกลไกที่เลื่อนการเรียกใช้ทรัพยากร (เช่น การเรียกฐานข้อมูล) ออกไปจนกว่าโค้ดส่วนนั้นจะถูกเรียกใช้งานจริงเท่านั้น แทนที่จะโหลดทุกอย่างพร้อมกันตั้งแต่ต้น เมื่อนำมาใช้กับความสัมพันธ์ของ Object หมายถึงการไม่ดึงข้อมูลที่ไม่ได้ใช้งานในรอบปัจจุบันออกมาทั้งหมด แต่จะรอ Query ข้อมูลเหล่านั้นเมื่อจำเป็นต้องแสดงผลเท่านั้น

🛠️ การแก้ปัญหา N+1 ด้วย PostgreSQL

ในทางปฏิบัติ การแก้ปัญหา N+1 ที่มีประสิทธิภาพที่สุดคือการปรับปรุง Query ให้เป็นแบบ Batch Loading หรือการใช้ JOIN/CTE (Common Table Expressions) ในระดับฐานข้อมูล PostgreSQL แทนที่จะปล่อยให้ ORM ทำการ Query แยกกันหลายครั้ง

ตัวอย่างโค้ด: การดึงข้อมูลแบบไม่มีประสิทธิภาพ (N+1)

สมมติว่า ORM ทำงานแบบ N+1:

SQL

-- Query 1: ดึงบทความทั้งหมด (N = 10) SELECT * FROM articles WHERE article_id IN (1, 2, 3, ...);

จากนั้นระบบจะวนลูปและ Query เพิ่ม:

SQL

-- Query 2: ดึงผู้เขียนสำหรับ Article 1 SELECT * FROM authors WHERE author_id = 1;

SQL

-- Query 3: ดึงความคิดเห็นสำหรับ Article 1 SELECT * FROM comments WHERE article_id = 1;

ถ้ามี 10 บทความ จะเกิด Query เพิ่มอีก 20 ครั้ง

ตัวอย่างโค้ด: การดึงข้อมูลแบบมีประสิทธิภาพ (Optimized JOIN/CTE)

เราสามารถรวมการ Query ทั้งหมดให้เหลือเพียงครั้งเดียวโดยใช้ JOIN ใน PostgreSQL:

SQL

SELECT 
    a.title, 
    u.username AS author_name, 
    c.comment_text 
FROM 
    articles a 
JOIN 
    authors u ON a.author_id = u.author_id 
LEFT JOIN 
    comments c ON a.article_id = c.article_id 
WHERE 
    a.article_id IN (1, 2, 3);

การใช้ JOIN ทำให้ PostgreSQL สามารถดึงข้อมูลที่เกี่ยวข้องกันทั้งหมดในครั้งเดียว (Single Query) ซึ่งลด Latency และภาระงานบนฐานข้อมูลได้อย่างมหาศาล

✅ บทสรุปและแนวทางปฏิบัติที่ดีที่สุด

การทำ Lazy Loading สำหรับความสัมพันธ์ของ Object ใน PHP ควรถูกจัดการอย่างชาญฉลาด โดยการให้ความสำคัญกับการ Query ที่มีประสิทธิภาพในระดับฐานข้อมูล (Database Level) เสมอ การใช้ JOIN หรือเทคนิค Batch Query ที่เหมาะสมกับ PostgreSQL จะช่วยให้เราสามารถลดจำนวน Round Trips ระหว่าง PHP และ PostgreSQL ได้อย่างมีนัยสำคัญ ทำให้แอปพลิเคชันไม่เพียงแค่ทำงานได้เร็วขึ้น แต่ยังมีความเสถียรสูงด้วย