Database Indexing เป็นเทคนิคสำคัญที่สุดในการเพิ่มประสิทธิภาพการ Query ข้อมูล Index ทำหน้าที่เหมือนสารบัญของหนังสือ ช่วยให้ฐานข้อมูลค้นหาข้อมูลได้เร็วขึ้นโดยไม่ต้องอ่านทุกแถวในตาราง ถ้าไม่มี Index ฐานข้อมูลจะต้องทำ Full Table Scan ทุกครั้งที่มี Query เข้ามา ซึ่งยิ่งตารางมีข้อมูลเยอะก็ยิ่งช้า
บทความนี้อธิบายหลักการทำงานของ Index ประเภทต่าง ๆ ของ Index วิธีสร้างและจัดการ Index สำหรับ MySQL/MariaDB และ PostgreSQL รวมถึงแนวทางปฏิบัติที่ดีในการออกแบบ Index เพื่อให้ Query ทำงานเร็วที่สุด
หลักการทำงานของ Index
Index คือโครงสร้างข้อมูลแยกที่ฐานข้อมูลสร้างขึ้นเพื่อเร่งความเร็วในการค้นหา เมื่อสร้าง Index บน Column ใด ฐานข้อมูลจะสร้างโครงสร้างเพิ่มเติม (โดยทั่วไปเป็น B-Tree) ที่เก็บค่าของ Column นั้นพร้อมตัวชี้ (Pointer) ไปยังแถวจริงในตาราง ทำให้สามารถค้นหาข้อมูลได้ใน O(log n) แทนที่จะเป็น O(n)
การมี Index ช่วยให้การอ่านข้อมูลเร็วขึ้น แต่ก็มีข้อแลกเปลี่ยน เมื่อมีการ INSERT, UPDATE หรือ DELETE ข้อมูล ฐานข้อมูลต้องอัพเดต Index ด้วยทุกครั้ง ทำให้การเขียนข้อมูลช้าลงเล็กน้อย และ Index ยังใช้พื้นที่ดิสก์เพิ่มเติมด้วย ดังนั้นต้องเลือกสร้าง Index อย่างเหมาะสม ไม่ใช่สร้างบนทุก Column
ประเภทของ Index
ฐานข้อมูลแต่ละระบบรองรับ Index หลายประเภท แต่ละประเภทเหมาะกับรูปแบบการ Query ที่แตกต่างกัน การเลือกประเภทที่ถูกต้องมีผลต่อประสิทธิภาพอย่างมาก
B-Tree Index (Default)
B-Tree เป็น Index ประเภท Default ที่ใช้บ่อยที่สุด เหมาะสำหรับการเปรียบเทียบแบบ =, <, >, <=, >=, BETWEEN และ LIKE ที่ขึ้นต้นด้วยค่าคงที่ (เช่น LIKE ‘abc%’) รองรับการเรียงลำดับ (ORDER BY) และการจัดกลุ่ม (GROUP BY) ได้ดี
Hash Index
Hash Index ใช้ Hash Function ในการค้นหา เหมาะสำหรับการเปรียบเทียบแบบ = เท่านั้น เร็วกว่า B-Tree สำหรับ Exact Match แต่ไม่รองรับ Range Query หรือ ORDER BY ใน MySQL เฉพาะ MEMORY Engine เท่านั้นที่รองรับ Hash Index ส่วน PostgreSQL รองรับใน Disk-based Table ได้
Full-Text Index
Full-Text Index ออกแบบมาสำหรับการค้นหาข้อความ เช่น ค้นหาคำใน Column ที่เก็บบทความหรือคำอธิบายสินค้า ทำงานโดยแยกข้อความเป็นคำ (Tokenize) แล้วสร้าง Inverted Index ทั้ง MySQL และ PostgreSQL รองรับ Full-Text Search แต่มีไวยากรณ์ที่แตกต่างกัน
Spatial Index (R-Tree)
Spatial Index ใช้สำหรับข้อมูลเชิงพื้นที่ เช่น พิกัด GPS, พื้นที่ทางภูมิศาสตร์ ใช้โครงสร้าง R-Tree ที่เหมาะกับการค้นหาแบบ “อยู่ในพื้นที่นี้หรือไม่” หรือ “หาจุดที่ใกล้ที่สุด”
สร้าง Index ใน MySQL/MariaDB
MySQL รองรับการสร้าง Index ได้หลายวิธี ทั้งตอนสร้างตารางและเพิ่มภายหลัง ตัวอย่างด้านล่างครอบคลุมรูปแบบที่ใช้บ่อยที่สุด
# สร้าง Index พื้นฐาน
CREATE INDEX idx_email ON users(email);
# สร้าง Unique Index (ไม่อนุญาตค่าซ้ำ)
CREATE UNIQUE INDEX idx_users_email ON users(email);
# สร้าง Composite Index (หลาย Column)
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
# สร้าง Index ตอนสร้างตาราง
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
category_id INT,
price DECIMAL(10,2),
INDEX idx_category (category_id),
INDEX idx_price (price),
INDEX idx_cat_price (category_id, price)
) ENGINE=InnoDB;
# สร้าง Full-Text Index
CREATE FULLTEXT INDEX idx_ft_name ON products(name);
# สร้าง Prefix Index (เฉพาะ N ตัวอักษรแรก — ประหยัดพื้นที่)
CREATE INDEX idx_name_prefix ON products(name(50));
# ดู Index ทั้งหมดของตาราง
SHOW INDEX FROM orders;
# ลบ Index
DROP INDEX idx_email ON users;
ALTER TABLE users DROP INDEX idx_email;
สร้าง Index ใน PostgreSQL
PostgreSQL มีระบบ Indexing ที่ยืดหยุ่นมาก รองรับ Index หลายประเภทและมีฟีเจอร์เพิ่มเติม เช่น Partial Index, Expression Index และ Concurrent Index Creation ที่ไม่ Block การเขียนข้อมูล
# สร้าง B-Tree Index (Default)
CREATE INDEX idx_email ON users(email);
# สร้าง Unique Index
CREATE UNIQUE INDEX idx_users_email ON users(email);
# สร้าง Composite Index
CREATE INDEX idx_orders_user_date ON orders(user_id, created_at);
# สร้าง Hash Index (เหมาะกับ Exact Match)
CREATE INDEX idx_email_hash ON users USING hash(email);
# สร้าง GIN Index (เหมาะกับ JSONB, Array, Full-Text)
CREATE INDEX idx_data_gin ON documents USING gin(data);
# สร้าง GiST Index (เหมาะกับ Spatial, Range)
CREATE INDEX idx_location ON stores USING gist(location);
# สร้าง Partial Index (Index เฉพาะแถวที่ตรงเงื่อนไข)
CREATE INDEX idx_active_users ON users(email) WHERE is_active = true;
# สร้าง Expression Index
CREATE INDEX idx_lower_email ON users(LOWER(email));
# สร้าง Index แบบ CONCURRENTLY (ไม่ Block Write)
CREATE INDEX CONCURRENTLY idx_orders_date ON orders(created_at);
# ดู Index ทั้งหมดของตาราง
SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'orders';
# ลบ Index
DROP INDEX idx_email;
DROP INDEX CONCURRENTLY idx_email; -- ไม่ Block
Composite Index และ Column Order
Composite Index (Index หลาย Column) เป็นเทคนิคที่ทรงพลังมาก แต่ลำดับของ Column มีความสำคัญมาก Index จะทำงานได้ดีเมื่อ Query ใช้ Column จากซ้ายไปขวาตามลำดับที่กำหนดใน Index (Leftmost Prefix Rule)
# สมมติมี Composite Index นี้
CREATE INDEX idx_abc ON orders(user_id, status, created_at);
# Query ที่ใช้ Index ได้เต็มที่
SELECT * FROM orders WHERE user_id = 1 AND status = 'active' AND created_at > '2026-01-01';
-- ใช้ Index ทั้ง 3 Column ✅
# Query ที่ใช้ Index ได้บางส่วน
SELECT * FROM orders WHERE user_id = 1 AND status = 'active';
-- ใช้ Index 2 Column แรก ✅
SELECT * FROM orders WHERE user_id = 1;
-- ใช้ Index Column แรก ✅
# Query ที่ใช้ Index ไม่ได้
SELECT * FROM orders WHERE status = 'active';
-- ข้าม user_id ไป ใช้ Index ไม่ได้ ❌
SELECT * FROM orders WHERE created_at > '2026-01-01';
-- ข้าม user_id และ status ไป ใช้ Index ไม่ได้ ❌
SELECT * FROM orders WHERE user_id = 1 AND created_at > '2026-01-01';
-- ใช้ Index ได้แค่ user_id (ข้าม status) ⚠️
Covering Index
Covering Index คือ Index ที่มี Column ครบทุกตัวที่ Query ต้องการ ทำให้ฐานข้อมูลสามารถตอบ Query ได้จาก Index โดยตรงโดยไม่ต้องอ่านข้อมูลจากตารางจริง (Index-Only Scan) ซึ่งเร็วกว่ามาก
# MySQL — ใช้ INCLUDE ไม่ได้ แต่ใช้ Composite Index แทน
CREATE INDEX idx_covering ON orders(user_id, status, total_amount);
# Query นี้จะเป็น Index-Only Scan
SELECT status, total_amount FROM orders WHERE user_id = 1;
-- ทุก Column ที่ SELECT และ WHERE อยู่ใน Index
# PostgreSQL — ใช้ INCLUDE เพิ่ม Column ที่ไม่ต้อง Search
CREATE INDEX idx_covering ON orders(user_id) INCLUDE (status, total_amount);
# Query นี้จะเป็น Index-Only Scan เช่นกัน
SELECT status, total_amount FROM orders WHERE user_id = 1;
วิเคราะห์การใช้ Index
การสร้าง Index แล้วไม่ตรวจสอบว่า Query ใช้ Index จริงหรือไม่ อาจไม่ได้ผลตามที่คาดหวัง ควรใช้ EXPLAIN เพื่อดูว่า Query Planner เลือกใช้ Index ตัวไหน
# MySQL — ดู Execution Plan
EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND status = 'active';
# ดูแบบละเอียด
EXPLAIN FORMAT=JSON SELECT * FROM orders WHERE user_id = 1;
# สิ่งที่ต้องดูใน EXPLAIN:
# - type: ref หรือ range = ใช้ Index, ALL = Full Table Scan
# - key: ชื่อ Index ที่ใช้ (NULL = ไม่ใช้ Index)
# - rows: จำนวนแถวที่ต้องอ่าน (ยิ่งน้อยยิ่งดี)
# - Extra: "Using index" = Covering Index
# PostgreSQL — ดู Execution Plan
EXPLAIN SELECT * FROM orders WHERE user_id = 1 AND status = 'active';
# ดูพร้อมเวลาจริง (ANALYZE)
EXPLAIN (ANALYZE, BUFFERS) SELECT * FROM orders WHERE user_id = 1;
# สิ่งที่ต้องดู:
# - Index Scan / Index Only Scan = ใช้ Index ✅
# - Seq Scan = Full Table Scan (อาจต้องสร้าง Index) ⚠️
# - Bitmap Index Scan = ใช้ Index แบบ Bitmap (ปกติ)
# - actual time / rows: เวลาและจำนวนแถวจริง
ตรวจสอบ Index ที่ไม่ได้ใช้งาน
Index ที่สร้างแล้วไม่ได้ใช้งานเปลืองพื้นที่ดิสก์และทำให้การเขียนข้อมูลช้าลง ควรตรวจสอบเป็นประจำและลบ Index ที่ไม่จำเป็นออก
# MySQL — ตรวจสอบ Index ที่ไม่ได้ใช้ (ต้องเปิด Performance Schema)
SELECT
object_schema AS db,
object_name AS tbl,
index_name,
count_star AS total_access
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
AND count_star = 0
AND object_schema = 'mydb'
ORDER BY object_name;
# PostgreSQL — ตรวจสอบ Index ที่ไม่ได้ใช้
SELECT
schemaname || '.' || relname AS table,
indexrelname AS index_name,
pg_size_pretty(pg_relation_size(i.indexrelid)) AS index_size,
idx_scan AS times_used
FROM pg_stat_user_indexes i
JOIN pg_index USING (indexrelid)
WHERE idx_scan = 0
AND NOT indisunique
ORDER BY pg_relation_size(i.indexrelid) DESC;
# ดูขนาด Index ทั้งหมดของตาราง (PostgreSQL)
SELECT
pg_size_pretty(pg_indexes_size('orders')) AS total_index_size,
pg_size_pretty(pg_table_size('orders')) AS table_size;
แนวทางปฏิบัติที่ดีในการสร้าง Index
การออกแบบ Index ที่ดีต้องคำนึงถึงรูปแบบ Query ที่ใช้จริง ไม่ใช่สร้าง Index มั่ว ๆ แนวทางด้านล่างช่วยให้ตัดสินใจได้ดีขึ้น
- สร้าง Index บน Column ที่ใช้ใน WHERE, JOIN, ORDER BY บ่อย ๆ — เริ่มจากดู Slow Query Log ว่า Query ไหนช้าที่สุด
- ใช้ Composite Index แทนหลาย Single Index — เช่น ถ้า Query ใช้ WHERE user_id = ? AND status = ? ควรสร้าง Index (user_id, status) แทน Index แยก 2 ตัว
- วาง Column ที่ Selectivity สูงก่อน — Column ที่มีค่าไม่ซ้ำกันมาก (เช่น user_id) ควรอยู่ก่อน Column ที่ค่าซ้ำกันเยอะ (เช่น status)
- ระวังการ Index Column ที่มีค่าซ้ำมาก — เช่น Column เพศ (M/F) หรือ Boolean มี Selectivity ต่ำ สร้าง Index แล้วอาจไม่คุ้ม
- ลบ Index ที่ซ้ำซ้อน — ถ้ามี Index (A, B) อยู่แล้ว ไม่ต้องมี Index (A) แยกเพราะ Leftmost Prefix ครอบคลุมอยู่แล้ว
- ตรวจสอบ Index ไม่ใช้งานเป็นประจำ — ลป Index ที่ไม่มี Query ใช้เพื่อลดภาระการเขียนข้อมูล
- อย่าสร้าง Index มากเกินไป — ตารางที่มีการเขียนบ่อย (High Write) ไม่ควรมี Index เยอะเกินไป
สิ่งที่ทำให้ Index ไม่ทำงาน
บางครั้งสร้าง Index แล้วแต่ Query ไม่ใช้ เป็นเพราะเขียน Query ในรูปแบบที่ทำให้ Optimizer ไม่สามารถใช้ Index ได้ ต้องระวังกรณีเหล่านี้
# ❌ ใช้ Function กับ Column ที่มี Index
SELECT * FROM users WHERE LOWER(email) = '[email protected]';
-- ต้องสร้าง Expression Index: CREATE INDEX idx_lower_email ON users(LOWER(email));
# ❌ ใช้ LIKE ที่ขึ้นต้นด้วย Wildcard
SELECT * FROM products WHERE name LIKE '%phone%';
-- B-Tree Index ใช้ไม่ได้ ต้องใช้ Full-Text Index แทน
# ❌ Type Mismatch
SELECT * FROM orders WHERE order_id = '12345';
-- ถ้า order_id เป็น INT แต่เปรียบเทียบกับ STRING จะเกิด Implicit Cast
# ❌ ใช้ OR กับ Column ต่างกัน
SELECT * FROM orders WHERE user_id = 1 OR total_amount > 1000;
-- Index อาจไม่ถูกใช้ ถ้าไม่มี Index ครอบคลุมทั้ง 2 Column
# ❌ ใช้ NOT IN หรือ != กับค่าจำนวนมาก
SELECT * FROM orders WHERE status NOT IN ('cancelled', 'refunded');
-- Optimizer อาจเลือก Full Table Scan แทน
# ✅ แก้ไข: เขียน Query ให้ใช้ Index ได้
SELECT * FROM users WHERE email = '[email protected]'; -- ตรงกับ Index
SELECT * FROM products WHERE name LIKE 'phone%'; -- Prefix Match ใช้ Index ได้
SELECT * FROM orders WHERE order_id = 12345; -- Type ตรงกัน
สรุป
Database Indexing เป็นพื้นฐานสำคัญในการเพิ่มประสิทธิภาพการ Query สิ่งสำคัญคือต้องเข้าใจหลักการทำงานของ B-Tree Index, Composite Index และ Covering Index เลือกสร้าง Index ตามรูปแบบ Query ที่ใช้จริง ตรวจสอบด้วย EXPLAIN ว่า Index ถูกใช้งานจริง และทำความสะอาด Index ที่ไม่ได้ใช้เป็นประจำ การออกแบบ Index ที่ดีสามารถทำให้ Query เร็วขึ้นหลายร้อยเท่าจาก Full Table Scan
แนะนำบริการ DE
การรันฐานข้อมูลที่มีข้อมูลจำนวนมากต้องการเซิร์ฟเวอร์ที่มี RAM เพียงพอสำหรับเก็บ Index ใน Memory และ SSD ที่มีความเร็วสูงสำหรับ I/O ที่เกิดจากการอ่าน Index Cloud VPS ของ DE รองรับการเลือก RAM และ SSD NVMe ตามความต้องการ พร้อม Root Access เต็มรูปแบบสำหรับ Tuning ฐานข้อมูลได้อิสระ
สำหรับโปรเจกต์ที่ต้องการความสะดวกโดยไม่ต้องจัดการเซิร์ฟเวอร์เอง Cloud Hosting ของ DE เป็นทางเลือกที่เหมาะสมพร้อม Managed Database และ Infrastructure ที่พร้อมใช้งานทันที

