ปรับ SQL Query ด้วย EXPLAIN และ Index แทนการเดาสุ่ม
เริ่ม optimization จาก execution plan จำนวนแถว และ pattern การกรองจริง ก่อนเพิ่ม index หรือเขียน query ใหม่ เพื่อแก้คอขวดโดยไม่สร้างภาระตอนเขียนข้อมูล

ภาพรวม
SQL Optimization ไม่ใช่การเดาว่า “ใส่ index เพิ่มน่าจะเร็วขึ้น” แต่คือการไล่หาคอขวดแบบมีหลักฐานก่อน แล้วค่อยแก้ให้ถูกจุด
เวลา query ช้า สิ่งแรกที่ควรถามไม่ใช่ “ต้องเขียน SQL ใหม่ไหม” แต่ควรถามว่า “ช้าตรงไหน และช้านี้กระทบ user จริงแค่ไหน”
Mental model ง่าย ๆ คือ วัดก่อน → หา query ที่ช้าจริง → ดูแผนการรัน → แก้ทีละจุด → วัดซ้ำ
ถ้าข้ามขั้นวัดก่อน เราอาจเสียเวลาปรับ query ที่ไม่มีใครใช้ หรือใส่ index เพิ่มจน write ช้าลง แต่ latency ของ user ไม่ดีขึ้นเลย ⚠️
เริ่มจาก slow query ที่กระทบ user จริง เช่น API /orders เปิดช้า 3 วินาที หน้า dashboard โหลดนาน หรือ job หลังบ้านทำให้ DB CPU พุ่ง
อย่าเริ่มจาก query ที่ “ดูน่ากลัว” ใน codebase อย่างเดียว เพราะ query ยาวไม่ได้แปลว่าช้า query สั้น ๆ แบบ SELECT * FROM users WHERE email = ? ก็ช้าได้ ถ้า table ใหญ่และไม่มี index ที่เหมาะสม
จุดเริ่มที่ดีสำหรับ backend/Node.js คือ log latency ต่อ endpoint ตัวอย่างเช่นเก็บว่า GET /api/orders ใช้เวลา 2200ms query หลักใน request ใช้เวลา 1900ms จำนวน rows ที่อ่าน 850,000 rows ส่งกลับจริง 20 rows
ตัวเลขนี้บอกอะไรเรา? ถ้าอ่าน 850,000 rows แต่ใช้จริง 20 rows ปัญหาอาจไม่ใช่ network แต่อาจเป็น row scan, missing index, filter ที่ใช้ index ไม่ได้ หรือ join ที่ทำให้ database ต้องอ่านข้อมูลเยอะเกินจำเป็น
จากนั้นไปดูเครื่องมือของ DB PostgreSQL ใช้ EXPLAIN หรือ EXPLAIN ANALYZE MySQL ใช้ EXPLAIN, slow query log หรือ Performance Schema
EXPLAIN ช่วยตอบว่า database “วางแผนจะทำอะไร” เช่นใช้ index ไหม scan ทั้ง table ไหม join ลำดับไหน ประมาณจำนวน rows เท่าไร
EXPLAIN ANALYZE ใน PostgreSQL จะรันจริงแล้วบอกเวลาจริงด้วย จึงเหมาะมากเวลาอยากเทียบก่อน/หลัง optimize แต่ต้องระวังกับ query ที่แก้ข้อมูล เช่น UPDATE/DELETE ควรทดสอบใน environment ที่ปลอดภัยก่อน
สิ่งที่ควรมองหาในแผนการรัน 🔍
- sequential scan บน table ใหญ่โดยไม่ตั้งใจ
- estimated rows กับ actual rows ต่างกันมาก
- sort หรือ group by ที่ใช้ memory/disk เยอะ
- join ที่สร้าง intermediate rows จำนวนมาก
- filter ที่เกิดหลังจากอ่าน rows ไปแล้วเยอะมาก
ตัวอย่าง pitfall ที่เจอบ่อย มี index แล้ว แต่ query ไม่ได้ใช้ index เช่น WHERE DATE(created_at) = '2026-07-07' เพราะเราเอา function ไปครอบ column DB อาจใช้ index บน created_at ไม่ได้เต็มที่
วิธีที่ดีกว่าคือเขียนเป็น range WHERE created_at >= '2026-07-07' AND created_at < '2026-07-08'
อีกตัวอย่างคือ LIKE '%abc%' ถ้ามี wildcard ด้านหน้า index ปกติช่วยได้จำกัด ต้องคิดใหม่ว่า search pattern นี้ควรใช้ full-text search trigram index หรือ search engine แยกไหม
Index ไม่ใช่ของฟรี มันช่วย read ได้ แต่เพิ่มต้นทุนตอน insert/update/delete และกิน storage ดังนั้นอย่าใส่ index ทุก column เพราะกลัวช้า ให้ใส่จาก evidence ว่า query ไหนอ่านเยอะและ filter/sort/join ด้วย column อะไร
Workflow ที่แนะนำสำหรับทีม backend 🧭
- เก็บ slow endpoint จาก monitoring หรือ log
- แยก query ที่กินเวลาหลักใน request
- รัน EXPLAIN/EXPLAIN ANALYZE
- ดู rows scanned, join, sort, index usage
- ปรับ query หรือ index ทีละอย่าง
- วัด latency ก่อน/หลังด้วย dataset ใกล้ production
ใน PostgreSQL ใช้ pg_stat_statements เพื่อดู query ที่กินเวลารวมสูง บาง query อาจไม่ได้ช้ามากต่อครั้ง แต่ถูกเรียกวันละล้านครั้ง รวมแล้วแพงกว่ query ที่ช้า 10 วินาทีแต่รันวันละครั้ง
ใน MySQL เปิด slow query log หรือดู Performance Schema จะช่วยเห็น query ที่เกิน threshold เช่นเกิน 500ms หรืออ่าน rows เยอะผิดปกติ
ตัวชี้วัดที่ควรดูร่วมกัน latency ต่อ query จำนวนครั้งที่ query ถูกเรียก rows examined / rows scanned rows returned CPU, memory, disk I/O ของ DB lock wait และ connection pool saturation
อย่าดูแค่ “query นี้ใช้เวลา 800ms” ต้องดูบริบทด้วย ถ้า endpoint สำคัญโดน user ทุกคน และ p95 สูง ควรแก้ก่อน ถ้าเป็น admin report ที่รันเดือนละครั้ง อาจไม่ใช่ priority แรก
สำหรับ Node.js backend อีกจุดที่พลาดบ่อยคือ N+1 query เช่นดึง users 100 คน แล้ววนไป query orders ทีละ user รวมเป็น 101 queries บางครั้งแต่ละ query เร็ว แต่รวมกันช้า แก้ได้ด้วย join, batch query, DataLoader pattern หรือปรับ API shape
สรุปคือ SQL Optimization ที่ดีไม่ใช่การจำ trick เยอะ ๆ แต่คือการมีนิสัยแบบ engineer: เริ่มจาก impact จริง ใช้ข้อมูลวัด อ่าน execution plan แก้ทีละจุด แล้ววัดซ้ำเสมอ ✅
ถ้าจำได้แค่ประโยคเดียว ให้จำว่า “อย่า optimize จากความรู้สึก ให้ optimize จากหลักฐาน”
แนวทางนำไปใช้
- นำแนวคิดจากโพสต์นี้ไปทดลองกับงานจริงของคุณ
- แยกเป็นขั้นตอนเล็ก ๆ แล้วทำทีละส่วน
- บันทึกผลลัพธ์และสิ่งที่เรียนรู้เพื่อต่อยอด
สรุป
เริ่ม optimization จาก execution plan จำนวนแถว และ pattern การกรองจริง ก่อนเพิ่ม index หรือเขียน query ใหม่ เพื่อแก้คอขวดโดยไม่สร้างภาระตอนเขียนข้อมูล