อ่าน EXPLAIN และออกแบบ Index ให้ Query เร็วขึ้น
เมื่อเว็บช้าและพบว่าคอขวดอยู่ที่ฐานข้อมูล ทางแก้ที่ได้ผลที่สุดมักไม่ใช่การเพิ่ม CPU หรือ RAM แต่คือการเพิ่ม Index ที่ถูกต้องหนึ่งตัว ซึ่งเปลี่ยน Query จากที่ใช้เวลาสองวินาทีเหลือสองมิลลิวินาทีได้
คู่มือนี้สอนอ่านผล EXPLAIN ซึ่งเป็นเครื่องมือที่บอกว่าฐานข้อมูลตัดสินใจทำงานอย่างไร แล้วออกแบบ Index ให้ตรงกับที่ Query ต้องการจริง พร้อมกฎการเรียงลำดับคอลัมน์ที่เป็นสาเหตุให้ Index จำนวนมากไม่ถูกใช้งาน
สิ่งที่ต้องเตรียม
- MySQL 8.0 หรือ MariaDB 10.5 ขึ้นไป
- Query ที่ช้าจริงจากระบบงาน ไม่ใช่ Query สมมติ
หาว่า Query ไหนช้าก่อนด้วย Slow Query Log
mysql -e "SET GLOBAL slow_query_log = ON;"
mysql -e "SET GLOBAL long_query_time = 1;"
mysql -e "SET GLOBAL log_queries_not_using_indexes = ON;"
sudo mysqldumpslow -s t -t 10 /var/log/mysql/slow.log
ขั้นตอนที่ 1: อ่านผล EXPLAIN
EXPLAIN SELECT * FROM orders
WHERE customer_id = 1234 AND status = 'paid'
ORDER BY created_at DESC LIMIT 20;
คอลัมน์ที่ต้องดูและความหมาย
type คือวิธีที่ใช้เข้าถึงข้อมูล เรียงจากดีที่สุดไปแย่ที่สุด
constและeq_refดีที่สุด อ่านแถวเดียวrefดี ใช้ Index หาแถวที่ตรงเงื่อนไขrangeใช้ได้ อ่านช่วงของ Indexindexไม่ดีนัก อ่าน Index ทั้งตัวALLแย่ที่สุด อ่านทั้งตาราง คือเป้าหมายที่ต้องกำจัด
key คือ Index ที่ถูกเลือกใช้จริง
หากเป็น NULL แปลว่าไม่ได้ใช้ Index เลย
rows คือจำนวนแถวที่คาดว่าต้องอ่าน
ตัวเลขนี้คือสิ่งที่ต้องทำให้เล็กที่สุด ถ้าตารางมีล้านแถวและ rows แสดงเป็นล้าน แปลว่ากำลังสแกนทั้งตาราง
Extra คือรายละเอียดเพิ่มเติมที่สำคัญ
Using indexดีมาก อ่านจาก Index อย่างเดียวไม่ต้องแตะตารางจริงUsing whereปกติUsing filesortต้องเรียงข้อมูลเพิ่ม ซึ่งช้าUsing temporaryต้องสร้างตารางชั่วคราว ซึ่งช้ามาก
ดูแผนแบบละเอียดพร้อมเวลาที่ใช้จริง
EXPLAIN ANALYZE SELECT * FROM orders
WHERE customer_id = 1234 AND status = 'paid'
ORDER BY created_at DESC LIMIT 20;
คำสั่งนี้รัน Query จริงแล้วรายงานเวลาที่ใช้ในแต่ละขั้น ต่างจาก EXPLAIN ธรรมดาที่แสดงเพียงแผนที่คาดไว้
ขั้นตอนที่ 2: กฎการเรียงคอลัมน์ใน Index แบบผสม
นี่คือหัวใจของการออกแบบ Index และเป็นจุดที่ทำผิดกันมากที่สุด กฎคือเรียงตามลำดับนี้
- คอลัมน์ที่เปรียบเทียบแบบเท่ากับ (
=) ก่อน - คอลัมน์ที่ใช้เรียงลำดับ (
ORDER BY) ถัดมา - คอลัมน์ที่เปรียบเทียบแบบช่วง (
>,<,BETWEEN) ไว้ท้ายสุด
สำหรับ Query ตัวอย่างข้างต้น Index ที่ถูกต้องคือ
CREATE INDEX idx_orders_cust_status_created
ON orders (customer_id, status, created_at);
เหตุผลคือ customer_id และ status ใช้เปรียบเทียบแบบเท่ากับจึงมาก่อน แล้ว created_at ใช้เรียงจึงตามมา ผลคือฐานข้อมูลอ่าน Index ตามลำดับที่เรียงไว้แล้วได้เลย ไม่ต้อง Using filesort
หากสลับลำดับเป็น (created_at, customer_id, status) Index จะแทบไม่ถูกใช้ เพราะเงื่อนไขแรกใน Query ไม่ได้อยู่ที่คอลัมน์แรกของ Index
ขั้นตอนที่ 3: หลักการคำนำหน้าซ้ายสุด
Index แบบผสมใช้ได้เฉพาะเมื่อ Query ใช้คอลัมน์เรียงจากซ้ายไปขวาต่อเนื่องกัน สมมติมี Index (a, b, c)
WHERE a = 1ใช้ได้WHERE a = 1 AND b = 2ใช้ได้WHERE a = 1 AND b = 2 AND c = 3ใช้ได้เต็มที่WHERE b = 2ใช้ไม่ได้ เพราะข้ามaWHERE a = 1 AND c = 3ใช้ได้แค่aเท่านั้น
ผลที่ตามมาคือ Index (a, b) ทำให้ Index (a) ซ้ำซ้อนและควรลบทิ้ง เพราะตัวแรกครอบคลุมอยู่แล้ว
ขั้นตอนที่ 4: หา Index ที่ไม่ได้ใช้และที่ซ้ำซ้อน
-- Index ที่ไม่เคยถูกใช้เลย
SELECT object_schema, object_name, index_name
FROM performance_schema.table_io_waits_summary_by_index_usage
WHERE index_name IS NOT NULL
AND count_star = 0
AND object_schema NOT IN ('mysql','performance_schema','sys')
ORDER BY object_schema, object_name;
-- ดู Index ทั้งหมดของตาราง พร้อมความหลากหลายของค่า
SELECT index_name, seq_in_index, column_name, cardinality
FROM information_schema.statistics
WHERE table_schema = 'shop' AND table_name = 'orders'
ORDER BY index_name, seq_in_index;
ค่า cardinality คือจำนวนค่าที่ไม่ซ้ำกันโดยประมาณ ยิ่งสูงยิ่งดีสำหรับ Index เพราะกรองได้แคบ Index บนคอลัมน์ที่มีแค่สองค่า เช่น is_active มักไม่คุ้มที่จะสร้างเดี่ยว ๆ
Index ทุกตัวมีต้นทุน มันทำให้การ INSERT และ UPDATE ช้าลงและกินพื้นที่ ลบตัวที่ไม่ได้ใช้ทิ้ง
DROP INDEX idx_unused ON orders;
ขั้นตอนที่ 5: Covering Index
เมื่อ Index มีทุกคอลัมน์ที่ Query ต้องการ ฐานข้อมูลไม่ต้องกลับไปอ่านตารางจริงเลย ซึ่งเร็วกว่ามาก
-- Query นี้ต้องการแค่สามคอลัมน์
SELECT customer_id, status, created_at FROM orders
WHERE customer_id = 1234;
-- Index นี้ครอบคลุมทั้งหมด จึงได้ Using index
CREATE INDEX idx_cover ON orders (customer_id, status, created_at);
ใน EXPLAIN จะเห็น Using index ในคอลัมน์ Extra ซึ่งเป็นสัญญาณที่ดีที่สุดที่จะเห็นได้
นี่คือเหตุผลที่ควรเลิกใช้ SELECT * การระบุเฉพาะคอลัมน์ที่ใช้จริงทำให้มีโอกาสได้ Covering Index
ขั้นตอนที่ 6: สิ่งที่ทำให้ Index ใช้ไม่ได้
-- ผิด ใช้ฟังก์ชันครอบคอลัมน์ ทำให้ Index ใช้ไม่ได้
SELECT * FROM orders WHERE YEAR(created_at) = 2026;
-- ถูก เขียนเป็นช่วงแทน
SELECT * FROM orders WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01';
-- ผิด ขึ้นต้นด้วยเครื่องหมายแทนที่
SELECT * FROM products WHERE name LIKE '%เสื้อ%';
-- ใช้ Index ได้ เพราะขึ้นต้นด้วยข้อความจริง
SELECT * FROM products WHERE name LIKE 'เสื้อ%';
-- ผิด ชนิดข้อมูลไม่ตรงกัน ทำให้ต้องแปลงทุกแถว
SELECT * FROM users WHERE phone = 0812345678;
-- ถูก ส่งเป็นข้อความให้ตรงกับชนิดคอลัมน์
SELECT * FROM users WHERE phone = '0812345678';
ข้อสุดท้ายเป็นสาเหตุที่พบบ่อยและหายาก เพราะ Query ยังให้ผลถูกต้องแต่ช้ามาก
ขั้นตอนที่ 7: อัปเดตสถิติเมื่อแผนดูผิดปกติ
ฐานข้อมูลเลือกแผนจากสถิติที่เก็บไว้ หากข้อมูลเปลี่ยนไปมากแต่สถิติยังเก่า แผนที่เลือกอาจแย่
ANALYZE TABLE orders;
SHOW INDEX FROM orders;
หากมั่นใจว่า Index ตัวไหนควรถูกใช้ บังคับได้ แต่ควรเป็นทางเลือกสุดท้าย
SELECT * FROM orders FORCE INDEX (idx_orders_cust_status_created)
WHERE customer_id = 1234;
ปัญหาที่พบบ่อย
สร้าง Index แล้วแต่ EXPLAIN ยังแสดง type ALL
ตรวจสามข้อ ลำดับคอลัมน์ตรงกับที่ Query ใช้หรือไม่ ชนิดข้อมูลของค่าที่เปรียบเทียบตรงกับคอลัมน์หรือไม่ และตารางเล็กเกินไปจนฐานข้อมูลเห็นว่าอ่านทั้งตารางเร็วกว่าหรือไม่ ซึ่งกรณีหลังไม่ใช่ปัญหา
Query เดียวกันบางครั้งเร็วบางครั้งช้า
มักเป็นเรื่องแคช ครั้งแรกอ่านจากดิสก์ ครั้งถัดไปอ่านจากหน่วยความจำ ตรวจว่า innodb_buffer_pool_size ใหญ่พอที่จะเก็บข้อมูลที่ใช้บ่อยไว้ได้
เพิ่ม Index แล้วระบบเขียนช้าลง
เป็นผลข้างเคียงปกติ ทุก Index ต้องถูกอัปเดตทุกครั้งที่เขียน ตารางที่เขียนหนักควรมี Index เท่าที่จำเป็นจริง
สร้าง Index บนตารางใหญ่แล้วระบบค้าง
ใช้การสร้างแบบไม่ล็อกตาราง ซึ่ง MySQL 8.0 รองรับ
CREATE INDEX idx_name ON orders (customer_id), ALGORITHM=INPLACE, LOCK=NONE;
และทำในช่วงที่มีผู้ใช้น้อยเสมอ
ต้องการให้ทีมวิศวกรช่วยวิเคราะห์ประสิทธิภาพฐานข้อมูล ติดต่อ THAI DATA CLOUD ได้ที่ https://thaidata.cloud/contact/
- Categories:
- Cloud
- Tags:
- Cloud
- Cloud Server
หมวดหมู่ที่น่าสนใจ
- Account Settings
- AD Server
- AI
- Alibaba Cloud
- Anti-Spam Gateway
- AWS Amazon Web Services
- Campaign
- CentOS/AlmaLinux
- Cloud
- Cloud Backup
- Cloud Communication
- Cloud Migration
- Cloud Security
- Cloud Server Management
- Cloud Solution
- Cloud Solution for Government
- Cloud Solutions by Industry
- Cloud Storage
- Cloud VPS App Plus +
- Cloud VPS DirectAdmin
- Cloud VPS Plesk
- CSR
- Cyber Security
- Cybersecurity
- Data Sovereignty
- Database Server
- DDoS
- Digital Tranformation
- Digital Transformation
- Direct Mail
- Directadmin
- Domainname
- Ecommerce
- ERP
- Generative AI
- Getting Started
- Google Cloud
- Google G Suite
- Huawei Cloud
- IT News
- Linux Server
- Managed Cloud Services
- Managed Service Provider
- Manual
- Microsoft
- Microsoft 365
- Microsoft Azure
- News
- On-premise
- Private Mail Server
- Promotion
- Recommend Solution (Enterprise)
- Server
- Sovereign Cloud
- THAI DATA CLOUD Platform
- Ubuntu
- Ubuntu
- Uncategorized
- VMware
- VPS Server
- Web Design
- Web Hosting
- Web Hosting (DirectAdmin)
- Web Hosting (Plesk)
- Web Technologies
- Windows Server
- Wordpress
- Zimbra
- เรื่องราวความประทับใจ
- โซลูชันสำหรับธุรกิจการผลิตและยานยนต์
- โซลูชันสำหรับธุรกิจการศึกษา
- โซลูชันสำหรับธุรกิจการเงิน
- โซลูชันสำหรับธุรกิจขนส่งและกระจายสินค้า
- โซลูชันสำหรับธุรกิจค้าปลีก
- โซลูชันสำหรับธุรกิจท่องเที่ยว
- โซลูชันสำหรับธุรกิจบริการสุขภาพและโรงพยาบาล
- โซลูชันสำหรับธุรกิจประกันภัย
- โซลูชันสำหรับธุรกิจพลังงานและสาธารณูปโภค
- โซลูชันสำหรับธุรกิจสื่อสารมวลชนและเอ็นเตอร์เทนเมนท์
- โซลูชันสำหรับธุรกิจอสังหาริมทรัพย์
- โซลูชันสำหรับธุรกิจเทคโนโลยี








