EXPLAIN Plan สำหรับ Query Analysis

EXPLAIN เป็นคำสั่งที่สำคัญที่สุดสำหรับการวิเคราะห์ประสิทธิภาพ Query ในฐานข้อมูล เมื่อ Query ทำงานช้า สิ่งแรกที่ควรทำคือดู Execution Plan ผ่าน EXPLAIN เพื่อเข้าใจว่าฐานข้อมูลเลือกทำงานอย่างไร ใช้ Index ตัวไหน สแกนกี่แถว และมีจุดคอขวดตรงไหน

บทความนี้อธิบายวิธีใช้ EXPLAIN ใน MySQL/MariaDB และ PostgreSQL อ่านผลลัพธ์แต่ละส่วน วิเคราะห์ปัญหา และปรับปรุง Query ให้เร็วขึ้นจากข้อมูลที่ได้

ทำไมต้องใช้ EXPLAIN

เมื่อเขียน SQL Query ฐานข้อมูลจะไม่ทำงานตามที่เขียนทุกตัวอักษร แต่จะส่ง Query ผ่าน Query Optimizer ที่วิเคราะห์หลายทางเลือกแล้วเลือกวิธีที่คิดว่าเร็วที่สุด บางครั้ง Optimizer เลือกวิธีที่ไม่ดีเท่าที่ควร เช่น ไม่ใช้ Index ทั้งที่มีอยู่ หรือเลือก Join Order ที่ไม่เหมาะสม EXPLAIN ช่วยให้เห็นว่า Optimizer ตัดสินใจอย่างไร ทำให้รู้ว่าต้องแก้ไขตรงจุดไหน

EXPLAIN ใน MySQL/MariaDB

MySQL แสดงผล EXPLAIN เป็นตารางที่มีหลาย Column แต่ละ Column ให้ข้อมูลเกี่ยวกับวิธีที่ Optimizer เลือกทำงาน ด้านล่างอธิบายวิธีใช้และความหมายของแต่ละ Column

วิธีใช้ EXPLAIN

# EXPLAIN พื้นฐาน
EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND status = 'active';

# EXPLAIN แบบ JSON (แสดงรายละเอียดมากกว่า)
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id = 1;

# EXPLAIN ANALYZE (MySQL 8.0.18+) — รัน Query จริงแล้วแสดงเวลา
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 1;

# ดู Warning เพิ่มเติม (เช่น Query ที่ถูก Optimize ใหม่)
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
SHOW WARNINGS;

อ่านผลลัพธ์ EXPLAIN

ผลลัพธ์ EXPLAIN ใน MySQL มี Column หลักที่ต้องดู ด้านล่างอธิบายแต่ละ Column พร้อมค่าที่พบบ่อย

# ตัวอย่างผลลัพธ์ EXPLAIN
+----+-------------+--------+------+---------------------+---------------------+---------+-------+------+-------+
| id | select_type | table  | type | possible_keys       | key                 | key_len | ref   | rows | Extra |
+----+-------------+--------+------+---------------------+---------------------+---------+-------+------+-------+
|  1 | SIMPLE      | orders | ref  | idx_user_id_status  | idx_user_id_status  | 4       | const |   25 | NULL  |
+----+-------------+--------+------+---------------------+---------------------+---------+-------+------+-------+

# Column สำคัญ:
# id          — ลำดับของ SELECT ใน Query (1 = หลัก, 2+ = Subquery)
# select_type — ประเภท SELECT: SIMPLE, PRIMARY, SUBQUERY, DERIVED, UNION
# table       — ชื่อตารางที่กำลังอ่าน
# type        — วิธีเข้าถึงข้อมูล (สำคัญที่สุด)
# possible_keys — Index ที่อาจใช้ได้
# key         — Index ที่เลือกใช้จริง (NULL = ไม่ใช้ Index)
# key_len     — ความยาว Index ที่ใช้ (บอกว่าใช้กี่ Column ของ Composite Index)
# ref         — Column หรือค่าคงที่ที่ใช้เปรียบเทียบกับ Index
# rows        — จำนวนแถวที่ต้องอ่าน (ค่าประมาณ)
# Extra       — ข้อมูลเพิ่มเติมเกี่ยวกับวิธีทำงาน

ค่า type — ระดับการเข้าถึงข้อมูล

Column type เป็นตัวบ่งชี้ที่สำคัญที่สุดใน EXPLAIN เรียงจากดีที่สุดไปแย่ที่สุด ถ้าเจอ ALL หรือ index ในตารางขนาดใหญ่ ควรพิจารณาสร้าง Index เพิ่ม

# เรียงจากดีที่สุด → แย่ที่สุด

# system  — ตารางมีแถวเดียว (ดีที่สุด)
# const   — ค้นหาด้วย Primary Key หรือ Unique Index ได้ผลลัพธ์แถวเดียว
EXPLAIN SELECT * FROM users WHERE id = 1;
-- type: const (เร็วมาก)

# eq_ref  — Join ด้วย Primary Key หรือ Unique Index (1 แถวต่อแถว)
EXPLAIN SELECT * FROM orders o JOIN users u ON u.id = o.user_id WHERE o.id = 100;
-- users table: eq_ref

# ref     — ค้นหาด้วย Non-Unique Index
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
-- type: ref (ดี)

# range   — สแกน Index ในช่วงที่กำหนด
EXPLAIN SELECT * FROM orders WHERE created_at BETWEEN '2026-01-01' AND '2026-03-31';
-- type: range (ดี)

# index   — สแกน Index ทั้งหมด (Full Index Scan)
EXPLAIN SELECT user_id FROM orders;
-- type: index (ปานกลาง — ดีกว่า ALL แต่ยังสแกนทั้งหมด)

# ALL     — Full Table Scan (แย่ที่สุด)
EXPLAIN SELECT * FROM orders WHERE notes LIKE '%urgent%';
-- type: ALL (ควรแก้ไข)

ค่า Extra ที่ต้องรู้

Column Extra ให้ข้อมูลเพิ่มเติมเกี่ยวกับวิธีที่ MySQL ประมวลผล Query บางค่าบอกว่าทำงานดี บางค่าเป็นสัญญาณเตือน

# ✅ ค่าที่ดี
# Using index         — Covering Index (ไม่ต้องอ่านตารางจริง)
# Using where         — กรองข้อมูลด้วย WHERE หลังอ่าน Index (ปกติ)
# Using index condition — Index Condition Pushdown (ICP) กรองที่ระดับ Storage Engine

# ⚠️ ค่าที่ควรระวัง
# Using temporary     — สร้างตารางชั่วคราว (อาจช้าถ้าข้อมูลเยอะ)
# Using filesort      — เรียงลำดับใน Memory หรือ Disk (ไม่ได้ใช้ Index สำหรับ ORDER BY)
# Using join buffer   — ไม่มี Index สำหรับ Join Condition

# ตัวอย่าง Query ที่ทำให้เกิด Using filesort
EXPLAIN SELECT * FROM orders WHERE user_id = 1 ORDER BY total_amount;
-- Extra: Using filesort (ถ้าไม่มี Index ที่ครอบคลุม ORDER BY)

# แก้ไขด้วยการสร้าง Index ที่ครอบคลุม
CREATE INDEX idx_user_amount ON orders(user_id, total_amount);
-- Extra จะเปลี่ยนเป็น: Using index condition หรือไม่มี filesort

EXPLAIN FORMAT=JSON

EXPLAIN FORMAT=JSON ให้ข้อมูลละเอียดกว่าแบบตาราง รวมถึง cost estimate ที่ช่วยเข้าใจว่า Optimizer คำนวณต้นทุนอย่างไร

EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id = 1 AND status = 'active'\G

# ผลลัพธ์สำคัญที่ต้องดู:
# {
#   "query_block": {
#     "select_id": 1,
#     "cost_info": {
#       "query_cost": "5.46"      ← ต้นทุนรวมของ Query
#     },
#     "table": {
#       "table_name": "orders",
#       "access_type": "ref",      ← เหมือน type ในแบบตาราง
#       "possible_keys": ["idx_user_status"],
#       "key": "idx_user_status",
#       "used_key_parts": ["user_id", "status"],  ← Column ของ Index ที่ใช้จริง
#       "rows_examined_per_scan": 25,
#       "rows_produced_per_join": 25,
#       "cost_info": {
#         "read_cost": "2.96",
#         "eval_cost": "2.50",
#         "prefix_cost": "5.46"
#       }
#     }
#   }
# }

# used_key_parts บอกว่า Composite Index ใช้กี่ Column จริง ๆ
# ถ้า Index มี 3 Column แต่ used_key_parts มีแค่ 1 → ใช้ Index ไม่เต็มที่

EXPLAIN ใน PostgreSQL

PostgreSQL แสดงผล EXPLAIN เป็น Tree Structure ที่อ่านจากล่างขึ้นบน แต่ละ Node แสดงวิธีดึงข้อมูล ต้นทุน และจำนวนแถว เมื่อใช้ ANALYZE จะได้เวลาจริงเพิ่มเติม

วิธีใช้ EXPLAIN

# EXPLAIN พื้นฐาน (แสดง Plan โดยไม่รัน Query)
EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND status = 'active';

# EXPLAIN ANALYZE (รัน Query จริงแล้วแสดงเวลา)
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 1;

# EXPLAIN พร้อม Buffer Statistics
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id = 1;

# EXPLAIN แบบ JSON (เหมาะกับ Tool วิเคราะห์)
EXPLAIN (FORMAT JSON) SELECT * FROM orders WHERE user_id = 1;

# EXPLAIN พร้อมทุกตัวเลือก
EXPLAIN (ANALYZE, BUFFERS, VERBOSE, FORMAT TEXT) SELECT * FROM orders WHERE user_id = 1;

อ่านผลลัพธ์ EXPLAIN

ผลลัพธ์ของ PostgreSQL แสดงเป็น Plan Tree อ่านจากล่างขึ้นบน แต่ละ Node มีข้อมูล cost, rows และ width

# ตัวอย่างผลลัพธ์
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 1 AND status = 'active';

-- Index Scan using idx_user_status on orders  (cost=0.43..8.45 rows=1 width=64) (actual time=0.035..0.042 rows=25 loops=1)
--   Index Cond: ((user_id = 1) AND (status = 'active'::text))
-- Planning Time: 0.152 ms
-- Execution Time: 0.068 ms

# ส่วนสำคัญที่ต้องดู:
# cost=0.43..8.45     — ต้นทุน (startup..total) หน่วยเป็น cost unit
#   0.43 = startup cost (เวลาก่อนส่งแถวแรก)
#   8.45 = total cost (เวลาทั้งหมด)
# rows=1              — จำนวนแถวที่คาดว่าจะได้ (ค่าประมาณ)
# width=64            — ขนาดเฉลี่ยของแถว (bytes)
# actual time         — เวลาจริง (มีเฉพาะเมื่อใช้ ANALYZE)
# rows=25             — จำนวนแถวจริง (เทียบกับ rows ที่ประมาณ)
# loops=1             — จำนวนรอบที่ทำงาน

ประเภท Scan ที่พบบ่อย

PostgreSQL มี Scan หลายประเภท แต่ละประเภทเหมาะกับสถานการณ์ที่แตกต่างกัน ไม่ใช่ว่า Seq Scan จะแย่เสมอไป ถ้าตารางเล็กมาก Seq Scan อาจเร็วกว่า Index Scan

# Seq Scan — อ่านทุกแถวในตาราง (Full Table Scan)
EXPLAIN SELECT * FROM orders WHERE notes LIKE '%urgent%';
-- Seq Scan on orders  (cost=0.00..2541.00 rows=50 width=64)
--   Filter: (notes ~~ '%urgent%'::text)
-- ⚠️ เหมาะกับตารางเล็กหรือต้อง Filter แถวจำนวนมาก

# Index Scan — ค้นหาผ่าน Index แล้วอ่านข้อมูลจากตาราง
EXPLAIN SELECT * FROM orders WHERE user_id = 1;
-- Index Scan using idx_user_id on orders  (cost=0.43..8.45 rows=25 width=64)
--   Index Cond: (user_id = 1)
-- ✅ ใช้ Index แล้วกลับไปอ่าน Heap (ตาราง) เพื่อดึง Column ที่เหลือ

# Index Only Scan — ตอบ Query ได้จาก Index โดยไม่ต้องอ่านตาราง
EXPLAIN SELECT user_id, status FROM orders WHERE user_id = 1;
-- Index Only Scan using idx_user_status on orders  (cost=0.43..4.45 rows=25 width=12)
-- ✅ เร็วที่สุด ไม่ต้องอ่าน Heap

# Bitmap Index Scan + Bitmap Heap Scan — ใช้ Index สร้าง Bitmap แล้วอ่านตาราง
EXPLAIN SELECT * FROM orders WHERE user_id IN (1, 2, 3, 4, 5);
-- Bitmap Heap Scan on orders  (cost=5.00..50.00 rows=125 width=64)
--   Recheck Cond: (user_id = ANY ('{1,2,3,4,5}'::integer[]))
--   ->  Bitmap Index Scan on idx_user_id  (cost=0.00..4.97 rows=125 width=0)
-- ✅ เหมาะเมื่อต้องดึงหลายช่วง หรือใช้ OR/IN

ประเภท Join ที่พบบ่อย

เมื่อ Query มี JOIN หลายตาราง EXPLAIN จะแสดงวิธี Join ที่ Optimizer เลือก แต่ละแบบมีข้อดีข้อเสียต่างกัน

# Nested Loop — วน Loop ตารางใน (Inner) สำหรับทุกแถวของตารางนอก (Outer)
EXPLAIN ANALYZE
SELECT o.*, u.name FROM orders o JOIN users u ON u.id = o.user_id WHERE o.id = 100;
-- Nested Loop  (cost=0.86..16.89 rows=1 width=96)
--   ->  Index Scan using orders_pkey on orders o  (cost=0.43..8.45 rows=1 width=64)
--         Index Cond: (id = 100)
--   ->  Index Scan using users_pkey on users u  (cost=0.43..8.44 rows=1 width=32)
--         Index Cond: (id = o.user_id)
-- ✅ ดีเมื่อตารางนอกมีผลลัพธ์น้อย

# Hash Join — สร้าง Hash Table จากตารางหนึ่ง แล้ว Probe จากอีกตาราง
EXPLAIN ANALYZE
SELECT o.*, u.name FROM orders o JOIN users u ON u.id = o.user_id;
-- Hash Join  (cost=100.00..3000.00 rows=100000 width=96)
--   Hash Cond: (o.user_id = u.id)
--   ->  Seq Scan on orders o  (cost=0.00..2000.00 rows=100000 width=64)
--   ->  Hash  (cost=50.00..50.00 rows=1000 width=32)
--         ->  Seq Scan on users u  (cost=0.00..50.00 rows=1000 width=32)
-- ✅ ดีเมื่อ Join ข้อมูลจำนวนมาก

# Merge Join — เรียงลำดับทั้งสองตารางแล้ว Merge
-- ✅ ดีเมื่อข้อมูลเรียงลำดับอยู่แล้ว (จาก Index)

BUFFERS — ดูการอ่าน Disk และ Cache

การเพิ่ม BUFFERS ใน EXPLAIN ANALYZE ช่วยให้เห็นว่า Query อ่านข้อมูลจาก Shared Buffer (Cache) หรือต้องอ่านจาก Disk จริง ซึ่งบอกได้ว่ามีปัญหาเรื่อง Memory หรือไม่

EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id = 1;

-- Index Scan using idx_user_id on orders  (cost=0.43..8.45 rows=25 width=64) (actual time=0.035..0.042 rows=25 loops=1)
--   Index Cond: (user_id = 1)
--   Buffers: shared hit=4            ← อ่านจาก Cache 4 blocks
-- Planning Time: 0.152 ms
-- Execution Time: 0.068 ms

# shared hit   = อ่านจาก Shared Buffer (Cache) — ดี ✅
# shared read  = อ่านจาก Disk — ช้ากว่า ⚠️
# shared written = เขียนกลับ Disk (dirty pages)
# temp read/written = ใช้ Temp File (ไม่พอ work_mem) ❌

# ถ้า shared read เยอะมาก → อาจต้องเพิ่ม shared_buffers
# ถ้า temp read/written มีค่า → ต้องเพิ่ม work_mem

วิเคราะห์ปัญหาจาก EXPLAIN

เมื่ออ่าน EXPLAIN ได้แล้ว ขั้นตอนต่อไปคือระบุปัญหาและแก้ไข ด้านล่างเป็นปัญหาที่พบบ่อยพร้อมวิธีแก้

ปัญหา: Full Table Scan บนตารางใหญ่

# MySQL: type = ALL
EXPLAIN SELECT * FROM orders WHERE customer_email = '[email protected]';
-- type: ALL, rows: 500000 ← สแกน 500,000 แถว!

# แก้ไข: สร้าง Index
CREATE INDEX idx_customer_email ON orders(customer_email);
-- type เปลี่ยนเป็น ref, rows: 5

# PostgreSQL: Seq Scan บนตารางใหญ่
EXPLAIN SELECT * FROM orders WHERE customer_email = '[email protected]';
-- Seq Scan on orders (cost=0.00..15000.00 rows=5 width=64)

# แก้ไข: สร้าง Index
CREATE INDEX idx_customer_email ON orders(customer_email);
-- เปลี่ยนเป็น Index Scan

ปัญหา: Row Estimate ไม่ตรงกับความเป็นจริง

# ปัญหา: Optimizer ประมาณ rows ผิดมาก
# EXPLAIN บอก rows=10 แต่ ANALYZE บอก actual rows=50000

# PostgreSQL — อัพเดต Statistics
ANALYZE orders;

# MySQL — อัพเดต Statistics
ANALYZE TABLE orders;

# ถ้ายังไม่ตรง ลองเพิ่มความละเอียดของ Statistics (PostgreSQL)
ALTER TABLE orders ALTER COLUMN status SET STATISTICS 500;
ANALYZE orders;

ปัญหา: Using temporary + Using filesort

# MySQL: เกิดเมื่อ GROUP BY หรือ ORDER BY ใช้ Column ที่ไม่มี Index
EXPLAIN SELECT user_id, COUNT(*) FROM orders GROUP BY user_id ORDER BY COUNT(*) DESC;
-- Extra: Using temporary; Using filesort

# แก้ไข: สร้าง Index ที่ครอบคลุม GROUP BY
CREATE INDEX idx_user_id ON orders(user_id);
-- ลด Using temporary ได้

# ถ้า ORDER BY ใช้ Column ต่างจาก GROUP BY อาจยังมี filesort
# พิจารณาว่ารับได้หรือไม่ ถ้าผลลัพธ์ GROUP BY มีจำนวนน้อย filesort ก็เร็ว

ปัญหา: Nested Loop กับตารางใหญ่

# PostgreSQL: Nested Loop ช้าเมื่อตารางนอกมีผลลัพธ์เยอะ
EXPLAIN ANALYZE
SELECT o.*, p.name FROM orders o JOIN products p ON p.id = o.product_id
WHERE o.created_at > '2026-01-01';

-- Nested Loop  (cost=0.43..500000.00 rows=200000 width=96)
--   actual time=0.05..4500.00 rows=200000
-- ⚠️ ช้ามากเพราะวน Loop 200,000 รอบ

# แก้ไข: ให้ Optimizer เลือก Hash Join แทน
# วิธี 1: เพิ่ม work_mem เพื่อให้ Hash Join ทำงานได้
SET work_mem = '256MB';

# วิธี 2: ปิด Nested Loop ชั่วคราว (ใช้ตอนทดสอบเท่านั้น)
SET enable_nestloop = off;
EXPLAIN ANALYZE SELECT ...;
SET enable_nestloop = on;  -- เปิดกลับ

เปรียบเทียบ EXPLAIN ก่อน-หลังปรับปรุง

ตัวอย่างจริงของการวิเคราะห์และปรับปรุง Query ด้วย EXPLAIN แสดงให้เห็นผลลัพธ์ก่อนและหลังการสร้าง Index

# === ก่อนปรับปรุง ===
EXPLAIN ANALYZE
SELECT o.id, o.total_amount, u.name, u.email
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'pending' AND o.created_at > '2026-01-01'
ORDER BY o.created_at DESC
LIMIT 50;

# ผลลัพธ์ (สมมติ):
# Sort  (cost=15000.00..15000.12 rows=50 width=96) (actual time=850.00..850.05 rows=50)
#   Sort Key: o.created_at DESC
#   ->  Hash Join  (cost=100.00..14500.00 rows=5000 width=96) (actual time=200.00..845.00 rows=5000)
#         ->  Seq Scan on orders o  (cost=0.00..14000.00 rows=5000 width=64) (actual time=0.05..800.00)
#               Filter: ((status = 'pending') AND (created_at > '2026-01-01'))
#               Rows Removed by Filter: 495000  ← สแกน 500,000 แถว กรองเหลือ 5,000
# Execution Time: 851.00 ms

# === สร้าง Index ===
CREATE INDEX idx_orders_status_date ON orders(status, created_at DESC);

# === หลังปรับปรุง ===
# ผลลัพธ์:
# Limit  (cost=0.43..25.00 rows=50 width=96) (actual time=0.05..0.50 rows=50)
#   ->  Nested Loop  (cost=0.43..2500.00 rows=5000 width=96) (actual time=0.05..0.48 rows=50)
#         ->  Index Scan using idx_orders_status_date on orders o  (cost=0.43..500.00 rows=5000)
#               Index Cond: ((status = 'pending') AND (created_at > '2026-01-01'))
#         ->  Index Scan using users_pkey on users u  (cost=0.43..0.45 rows=1)
# Execution Time: 0.55 ms  ← จาก 851ms เหลือ 0.55ms (เร็วขึ้น 1,500 เท่า)

เครื่องมือช่วยวิเคราะห์ EXPLAIN

การอ่าน EXPLAIN Plan ด้วยตาอาจยากสำหรับ Query ที่ซับซ้อน มีเครื่องมือออนไลน์ที่ช่วย Visualize และวิเคราะห์ Plan ให้อัตโนมัติ

  • explain.dalibo.com — เครื่องมือ Visualize สำหรับ PostgreSQL รองรับ EXPLAIN ANALYZE แสดงเป็น Diagram พร้อมไฮไลท์จุดที่ช้า
  • explain.depesz.com — อีกเครื่องมือสำหรับ PostgreSQL แสดง Plan เป็นตารางพร้อมสีที่บอกระดับความช้า
  • MySQL Workbench — มี Visual Explain ในตัว แสดง Plan เป็น Diagram สำหรับ MySQL
  • Percona Toolkit (pt-query-digest) — วิเคราะห์ Slow Query Log แล้วสรุป Query ที่ช้าที่สุดพร้อม EXPLAIN

แนวทางปฏิบัติในการใช้ EXPLAIN

  • ใช้ EXPLAIN กับทุก Query ที่ช้าเกิน Threshold ที่ตั้งไว้ เช่น ช้าเกิน 100ms ใน Slow Query Log
  • เปรียบเทียบ rows ที่ประมาณ กับ actual rows ที่ได้จาก ANALYZE ถ้าต่างกันมากให้อัพเดต Statistics
  • ดู type หรือ Scan Type เป็นอันดับแรก ถ้าเป็น ALL หรือ Seq Scan บนตารางใหญ่ ให้พิจารณาสร้าง Index
  • ตรวจ Extra ว่ามี Using temporary หรือ Using filesort หรือไม่ ถ้ามีและ Query ช้า ให้ปรับ Index ให้ครอบคลุม ORDER BY และ GROUP BY
  • ใช้ EXPLAIN ANALYZE อย่างระวังบน Production เพราะมัน รัน Query จริง ถ้า Query เป็น UPDATE หรือ DELETE ควรใช้ใน Transaction แล้ว Rollback
  • ทดสอบ EXPLAIN กับข้อมูลที่ใกล้เคียง Production เพราะ Optimizer ตัดสินใจจาก Statistics ข้อมูลน้อยอาจได้ Plan ต่างจากข้อมูลจริง

สรุป

EXPLAIN เป็นเครื่องมือที่ขาดไม่ได้สำหรับการวิเคราะห์และปรับปรุง Query ใน MySQL ให้ดูค่า type, key, rows และ Extra เป็นหลัก ส่วนใน PostgreSQL ให้ดูประเภท Scan, cost estimate และใช้ ANALYZE กับ BUFFERS เพื่อดูเวลาจริงและการใช้ Cache สิ่งสำคัญคือต้องเปรียบเทียบค่าประมาณกับค่าจริงเสมอ และทดสอบกับข้อมูลที่ใกล้เคียง Production เพื่อให้ได้ผลลัพธ์ที่แม่นยำ

แนะนำบริการ DE

การวิเคราะห์และ Tuning Query ต้องการเซิร์ฟเวอร์ที่มี RAM เพียงพอสำหรับ Buffer Cache และ CPU ที่แรงพอสำหรับประมวลผล Query ที่ซับซ้อน Cloud VPS ของ DE รองรับการเลือก RAM และ CPU ตามความต้องการ พร้อม Root Access เต็มรูปแบบสำหรับตั้งค่า shared_buffers, work_mem และ Parameter อื่น ๆ ของฐานข้อมูลได้อิสระ

สำหรับโปรเจกต์ที่ไม่ต้องการจัดการเซิร์ฟเวอร์เอง Cloud Hosting ของ DE เป็นทางเลือกที่สะดวกพร้อม Managed Database ที่ตั้งค่ามาอย่างเหมาะสมสำหรับการใช้งานทั่วไป