ปรับแต่งฐานข้อมูลเว็บไซต์ standalone: ดู slow query log, index และโครงสร้างตาราง
1. ทำไมฐานข้อมูล WordPress ถึงช้า
WordPress ใช้ MySQL เป็นฐานข้อมูล โครงสร้างตารางเริ่มต้นรันเว็บเล็กไม่มีปัญหา แต่เมื่อเนื้อหาเยอะขึ้นจะยิ่งช้า สาเหตุไม่ได้อยู่ที่การตั้งค่าเซิร์ฟเวอร์ แต่อยู่ที่ปัญหาการออกแบบ 2 จุด: ตาราง wp_postmeta เก็บ custom field ทั้งหมดไว้ในตาราง key-value เดียว การ query พารามิเตอร์ของสินค้าหนึ่งชิ้นต้อง JOIN หลายครั้ง ส่วนตาราง wp_options เอาค่าตั้งค่าทั้งหมดมารวมกัน ทุกครั้งที่โหลดหลังบ้านต้องสแกนทั้งตาราง
หนึ่งหน้า WordPress โดยเฉลี่ย query ฐานข้อมูล 12 ถึง 20 ครั้ง ส่วนใหญ่เป็นการ query wp_postmeta เมื่อคุณติดตั้งปลั๊กอิน 10 ตัว แต่ละตัวเขียน custom field ลง wp_postmeta ตารางนี้อาจบวมถึงหลายแสนแถว ถึงตอนนั้นแค่ request หน้าเว็บที่มี meta_query ก็ต้องสแกนในหลายแสนแถว slow query จึงเกิดขึ้นเอง
| แหล่งที่มาของ slow query ที่พบบ่อย | ตารางที่เกี่ยวข้อง | อาการที่พบ | สัดส่วน |
|---|---|---|---|
| กรองบทความตาม custom field | wp_postmeta | หน้า กรองสินค้า โหลดเกิน 3 วินาที | ประมาณ 40% |
| โหลดการตั้งค่าปลั๊กอินหลังบ้าน | wp_options | หลังบ้านจอขาวหลายวินาทีทุกครั้งที่บันทึก | ประมาณ 20% |
| หน้า archive ของ tag/หมวดหมู่ | wp_term_relationships | หน้า archive ช้าลงเมื่อมีบทความเยอะ | ประมาณ 15% |
| query ตาราง comment และ user | wp_comments/wp_users | รายการ comment หรือการจัดการ user ช้า | ประมาณ 10% |
| query ซับซ้อนของปลั๊กอินเอง | ตารางที่ปลั๊กอินสร้างเอง | หน้า ฟีเจอร์ ใดหน้าหนึ่งช้าเป็นพิเศษ | ประมาณ 15% |
เมื่อฐานข้อมูลช้า page cache ช่วยบังบางส่วนได้ เมื่อ page cache hit ก็ไม่ query ฐานข้อมูลเลย แต่เมื่อ cache miss (เพิ่งเผยแพร่บทความ เข้าหน้าที่ยังไม่ถูก cache) slow query ก็จะโผล่ออกมา ดังนั้นการปรับแต่งฐานข้อมูลและกลยุทธ์ cache ต้องดูคู่กัน ดูที่กลยุทธ์แคช 3 ชั้นสำหรับเว็บไซต์แบรนด์ หากฐานข้อมูลช้าถึงขั้นกระทบ Core Web Vitals ดูลำดับความสำคัญในการปรับแต่งที่การปรับแต่ง Core Web Vitals。
2. Slow query log: หา SQL ที่ช้าอย่างไร
การปรับแต่งฐานข้อมูลให้เร็วขึ้นนั้น ขั้นตอนแรกไม่ใช่การเพิ่ม index แต่คือการหาว่าจริงๆ แล้ว SQL ตัวไหนที่ช้า การเดาด้วยตาเปล่าเดาไม่ได้หรอก — คุณอาจคิดว่าเป็น query ตัวหนึ่งที่ช้า แต่จริงๆ อาจเป็นอีกตัวก็ได้ MySQL มี slow query log ในตัว เปิดแล้วมันจะบันทึก SQL ที่ใช้เวลาเกิน threshold ไว้ทั้งหมด
ใน config ของ MySQL (my.cnf หรือ my.ini) เพิ่มสามบรรทัด: slow_query_log=1, long_query_time=1, slow_query_log_file=/var/log/mysql/slow.log ความหมายคือ เปิด slow log, บันทึก query ที่ใช้เวลาเกิน 1 วินาที, เขียน log ไปยังไฟล์ที่กำหนด ปล่อยให้รันหนึ่งวัน แล้วใช้เครื่องมือ mysqldumpslow หรือ pt-query-digest รวม log ตามประเภทของ query เพื่อหา TOP 10 ของ SQL ที่ช้าที่สุด
| ขั้นตอน | การดำเนินการ | เครื่องมือ/คำสั่ง | เป้าหมาย |
|---|---|---|---|
| 1. เปิด slow log | แก้ my.cnf เพิ่มสามบรรทัด | รีสตาร์ท MySQL ให้มีผล | บันทึก slow SQL |
| 2. รันหนึ่งวัน | เข้าเว็บไซต์ตามปกติหนึ่งวัน | —— | เก็บ slow query จริง |
| 3. รวมและวิเคราะห์ | รวมตาม SQL fingerprint | mysqldumpslow -s t | หา TOP 10 |
| 4. EXPLAIN ทีละตัว | ดู execution plan | EXPLAIN คำสั่ง SQL | ตัดสินว่าใช้ index หรือไม่ |
| 5. ทดสอบซ้ำหลังปรับแต่ง | เทียบเวลาก่อนและหลังปรับแต่ง | SHOW PROFILE หรือจับเวลา | ยืนยันผลลัพธ์ |
ตัวอย่างสถานการณ์: คุณพบว่าหน้า filter สินค้าช้ามาก เปิด slow log ดู ปรากฏว่า TOP 1 slow query คือ SQL ที่มี meta_query ค้นหาคustom field ตัวหนึ่งในตาราง wp_postmeta วิเคราะห์ด้วย EXPLAIN คอลัมน์ type แสดง ALL (full table scan) คอลัมน์ rows แสดงว่าสแกน 120,000 แถว — SQL ตัวนี้สแกนตาราง wp_postmeta ทุกครั้ง ถึงตอนนี้คุณก็รู้แล้วว่าต้องเพิ่ม index ให้กับคอลัมน์ meta_key ไม่ใช่ไปอัปเกรดเซิร์ฟเวอร์
slow query log ไม่ควรเปิดทิ้งไว้นาน เพราะตัวมันเองมี IO overhead เก็บข้อมูลครบหนึ่งวันแล้วก็ปิด หรือปรับ threshold เป็น 2 วินาทีเพื่อบันทึกเฉพาะที่ช้าที่สุด สำหรับการ monitor ประจำวันใช้เครื่องมือ monitor ประสิทธิภาพฐานข้อมูล (เช่น Prometheus+Grafana) ดูที่เช็กลิสต์การมอนิเตอร์และแจ้งเตือนเว็บไซต์。
สาม、การปรับแต่ง index: ฟิลด์ไหนควรเพิ่ม index
สองคอลัมน์ที่สำคัญที่สุดในผลลัพธ์ EXPLAIN คือ type และ rows ถ้าคอลัมน์ type แสดง ALL หมายความว่า query นี้สแกนทั้งตาราง ต้องเพิ่ม index ถ้าแสดง ref หรือ eq_ref หมายความว่าใช้ index แล้ว ไม่ต้องแตะ คอลัมน์ rows แสดงว่า MySQL ประเมินว่าจะสแกนกี่แถว ตัวเลขยิ่งมากยิ่งอันตราย — สแกนไม่เกิน 10,000 แถวยังพอไหว สแกนเกิน 100,000 แถวขึ้นไปควรปรับแต่งแล้ว
ตารางที่คุ้มค่าเพิ่ม index มากที่สุดในฐานข้อมูล WordPress คือตาราง wp_postmeta ตารางนี้โดยค่าเริ่มต้นมีแค่ index ของ post_id แต่ query จริงมัก filter ตาม meta_key และ meta_value เพิ่ม composite index (meta_key, meta_value) query ที่ filter ตาม custom field จะเร็วขึ้นกว่า 10 เท่า สังเกตว่าต้องเป็น composite index ไม่ใช่เพิ่ม index ให้ meta_key เดี่ยวๆ — ถ้าเพิ่ม meta_key เดี่ยวๆ เมื่อ query มีการ filter ด้วย meta_value ก็ยังต้องกลับไปอ่านตารางอยู่ดี
| ตาราง | สถานการณ์ query | index ที่แนะนำ | ผลลัพธ์ |
|---|---|---|---|
| wp_postmeta | filter ตาม meta_key+meta_value | (meta_key, meta_value(191)) | เร็วขึ้นกว่า 10 เท่า |
| wp_postmeta | ค้นหา meta ตาม post_id | มี index ของ post_id อยู่แล้ว | ไม่ต้องแตะ |
| wp_options | ค้นหา config ตาม option_name | มี index ของ option_name อยู่แล้ว | ไม่ต้องแตะ |
| wp_comments | ค้นความคิดเห็นตาม post_id | มี index ของ comment_post_ID อยู่แล้ว | ไม่ต้องแตะ |
| wp_posts | ค้นตาม post_type+post_status | มี composite index อยู่แล้ว | ไม่ต้องแตะ |
| ตารางที่ปลั๊กอินสร้างเอง | กรองตามวันที่/สถานะ | ดู EXPLAIN แล้วตัดสินใจ | เพิ่มตามความจำเป็น |
ข้อคิดที่ขัดกับสัญชาตญาณ: หลายคนเห็น query ช้าแล้วรีบเพิ่ม index ทันที แต่ index ไม่ได้ฟรี ทุก index กินพื้นที่ดิสก์ และทุกครั้งที่ INSERT หรือ UPDATE MySQL ต้องอัปเดต index ทั้งหมดด้วย ตารางอย่าง wp_options ที่เขียนบ่อยอ่านน้อย (เซฟการตั้งค่าหลังบ้านทีก็เขียนที) การเพิ่ม index มั่วๆ กลับทำให้เขียนช้าลง วิธีที่ถูกคือ ตารางที่อ่านบ่อยเขียนน้อย (wp_postmeta, wp_posts) เพิ่ม index ได้เต็มที่ ส่วนตารางที่เขียนบ่อยอ่านน้อย (wp_options, wp_comments) ต้องระวัง เพิ่มได้ก็ต่อเมื่อจำเป็นจริงๆ
加索引之前一定要先在staging环境测试。直接在生产库ALTER TABLE加索引,大表可能锁表几分钟,网站直接502。生产环境加索引用pt-online-schema-change或gh-ost这类在线DDL工具,不锁表。环形山在出海站数据库上的维护经验见 环形山技术栈与四个自研插件。
สี่ โครงสร้างตาราง: กับดักของ wp_postmeta และ wp_options
บางปัญหาเพิ่ม index แล้วแก้ไม่ได้ ต้องเปลี่ยนโครงสร้างตาราง การออกแบบ key-value ของ wp_postmeta ก็เป็นปัญหาประเภทนี้ มันเก็บ custom field ทั้งหมดไว้ในตารางเดียว จะดึงสเปกสินค้าตัวหนึ่งต้อง JOIN หลายรอบ พอสินค้าหลักพันตัวและ custom field หลายสิบตัว ตารางนี้จะบวมถึงหลายแสนแถว ผลตอบแทนจากการ optimize index ก็ถึงเพดาน
ตรงนี้มีสองแนวทาง แนวทางแรกใช้ตาราง custom แทน postmeta แยกตารางสำหรับ custom field ที่ query บ่อย เก็บตามชนิดของ field แล้ว query ตรงไปที่ตารางนั้น แนวทางที่สองใช้กลไก autoload ของ wp_options ใน WordPress ตั้งค่า config ที่อ่านบ่อยเขียนน้อยให้ autoload เลี่ยงการ query ตาราง options ทุกครั้ง สำหรับเว็บ B2B แนวทางแรกเหมาะกับเว็บที่พารามิเตอร์สินค้าซับซ้อน แนวทางที่สองเหมาะกับเว็บที่ติดปลั๊กอินเยอะ
| คำถาม | อาการ | แนวทางปรับปรุง | ความเสี่ยง |
|---|---|---|---|
| wp_postmeta บวม | หน้าตัวกรองช้า ตารางเกิน 1 แสนแถว | สร้างตาราง custom สำหรับ field ที่ query บ่อย | ต้องแก้โค้ด ต้องทดสอบ |
| wp_options autoload มากเกินไป | หลังบ้านช้า autoload เกิน 1MB | ล้าง config ของปลั๊กอินที่ไม่ได้ใช้ | สำรองก่อนลบ |
| wp_posts มี draft/revision มากเกินไป | รายการบทความช้า revision ขยะสะสม | ล้าง revision และ draft อัตโนมัติ | ยืนยันก่อนลบ |
| wp_commentmeta บวม | รายการความคิดเห็นช้า | ปิดการบันทึกสแปมคอมเมนต์อัตโนมัติ | ผลกระทบไม่มาก |
| ตาราง wp_users ใหญ่ | จัดการผู้ใช้ช้า | ล้างผู้ใช้ที่ไม่ active | ระวังลบผิด |
ตัวอย่างสถานการณ์: เว็บไซต์บริษัทเครื่องจักรอุตสาหกรรมแห่งหนึ่งมีสินค้า 50 รายการ แต่ละรายการมีพารามิเตอร์กำหนดเอง 20 ตัว (วัสดุ กำลังไฟ ขนาด ความแม่นยำ ฯลฯ) พารามิเตอร์ทั้งหมดนี้เก็บอยู่ใน wp_postmeta การเปิดหน้าสินค้าเพียงหน้าเดียวต้อง JOIN wp_postmeta ถึง 20 ครั้ง วิธีที่ถูกต้องคือสร้างตารางกำหนดเอง wp_product_specs แล้วเก็บพารามิเตอร์ 20 ตัวเป็นคอลัมน์ในแถวเดียว เวลาดึงพารามิเตอร์สินค้าก็แค่ SELECT ครั้งเดียว ไม่ต้อง JOIN วิธีนี้ใช้แรงพัฒนาไม่มาก แต่ประสิทธิภาพที่ได้เพิ่มขึ้นหลายเท่าตัว
ก่อนเปลี่ยนโครงสร้างตารางต้องสำรองฐานข้อมูลก่อน ดูขั้นตอนสำรองและกู้คืนได้ที่การสำรองและกู้คืนเว็บไซต์ หากฐานข้อมูลเคยถูกเจาะ ดูขั้นตอนล้างและเสริมความปลอดภัยได้ที่การจัดการหลังเว็บไซต์ถูกแฮ็ก。
ห้า การตรวจสอบและดูแลหลังปรับแต่ง
หลังเพิ่มดัชนีหรือเปลี่ยนโครงสร้างตารางแล้ว ยังนับว่าเสร็จไม่ได้ ต้องทำสามอย่าง อย่างแรก รัน EXPLAIN กับ slow SQL เดิมที่ยังไม่ปรับแต่งอีกครั้ง เพื่อยืนยันว่าคอลัมน์ type เปลี่ยนจาก ALL เป็น ref หรือ eq_ref อย่างที่สอง เทียบเวลาที่ใช้จริงก่อนและหลังปรับแต่ง เพื่อยืนยันว่า slow query เร็วขึ้นจริง อย่างที่สาม เฝ้าดูหนึ่งสัปดาห์ว่ามี slow query ใหม่โผล่ใน slow query log หรือไม่ เพราะการปรับแต่งมักย้ายคอขวดจากที่หนึ่งไปอีกที่หนึ่ง
| รายการตรวจสอบ | วิธีตรวจสอบ | เกณฑ์ผ่าน | การจัดการกรณีไม่ผ่าน |
|---|---|---|---|
| แผนการทำงานของ EXPLAIN | รัน EXPLAIN กับ slow SQL อีกครั้ง | type ไม่เป็น ALL และ rows ลดลงหนึ่งหลัก | ดัชนีไม่ทำงาน ตรวจสอบชนิดของฟิลด์ |
| เวลาที่ใช้ในการ query จริง | จับเวลาด้วย SQL โดยตรง | ลดจาก 1 วินาทีขึ้นไปเหลือไม่เกิน 100 มิลลิวินาที | ดัชนีไม่ถูกต้อง วิเคราะห์ใหม่ |
| slow query log | รัน 3 วันแล้วดู log | slow query เดิมหายไปหรือลดลงมาก | มี slow query ใหม่ ปรับแต่งต่อ |
| ประสิทธิภาพการเขียน | วัดเวลาของ INSERT/UPDATE | การเขียนไม่ช้าลงอย่างเห็นได้ชัด | ใส่ดัชนีมากเกินไป ลบดัชนีที่ซ้ำซ้อน |
| เวลาของหน้าเว็บ | คะแนน Lighthouse | LCP ตอน cache miss<1.5秒 | ยังมีคอขวดอื่นอีก |
การปรับแต่งฐานข้อมูลไม่ใช่งานที่ทำครั้งเดียว เนื้อหาที่เพิ่มขึ้น อัปเดตปลั๊กอิน เปลี่ยนธีม ล้วนทำให้เกิด slow query ใหม่ แนะนำให้วิเคราะห์ slow query log เดือนละครั้ง ดูว่า TOP 10 เปลี่ยนไปหรือไม่ ในงานดูแลประจำวัน การปรับแต่งฐานข้อมูลเป็นรายการที่ต้องทำเสมอ ดูได้ที่งาน 6 อย่างที่ต้องทำทุกเดือนในช่วงดูแลเว็บไซต์ กรอบการเลือกเทคโนโลยีทั้งหมดของเว็บไซต์อิสระดูได้ที่เทคโนโลยีสแตกของ crater ส่วนการอัปเกรดเวอร์ชัน PHP ให้สอดคล้องกับการตั้งค่าฐานข้อมูลดูได้ที่คู่มือดูแลเว็บไซต์。
FAQ คำถามที่พบบ่อย
เปิด MySQL slow query log ของ WordPress อย่างไร?
ทำไมตาราง wp_postmeta ถึงช้าขนาดนี้?
การเพิ่มดัชนีจะทำให้เว็บช้าลงไหม
ดูผลลัพธ์ EXPLAIN ยังไง
การเพิ่มดัชนีบนฐานข้อมูลโปรดักชันจะล็อกตารางไหม
ควรทำ optimization ฐานข้อมูลบ่อยแค่ไหน
ทิ้งท้าย
เรื่อง optimization ฐานข้อมูล ทางที่ผิดที่คนมักเดินคือพอช้าก็อัปเกรดเซิร์ฟเวอร์ จาก 1 คอร์ 2GB ไปเป็น 4 คอร์ 8GB จ่ายเงินเป็นสองเท่า แต่ slow query ก็ยังช้าอยู่ เพราะคอขวดไม่ได้อยู่ที่ CPU แต่อยู่ที่การสแกนทั้งตารางที่ไม่ใช้ดัชนี ลำดับที่ถูกต้องเสมอคือ เปิด slow log หาให้เจอว่าคิวรีไหนช้า แล้วใช้ EXPLAIN ดูแผนการทำงาน สุดท้ายจึงตัดสินใจว่าจะเพิ่มดัชนีหรือเปลี่ยนโครงสร้างตาราง
ทำ 3 ขั้นตอนนี้: ขั้นแรก เปิด slow query log ทิ้งไว้หนึ่งวัน แล้วใช้ mysqldumpslow หา TOP 10; ขั้นที่สอง EXPLAIN ทีละคำสั่ง ตัวไหน type=ALL ก็เพิ่ม index โดย wp_postmeta ให้เพิ่ม composite index (meta_key,meta_value) ก่อน; ขั้นที่สาม ทดสอบซ้ำหลัง optimize เพื่อยืนยันว่า slow SQL ลดจาก 1 วินาทีเหลือไม่เกิน 100 มิลลิวินาที ทำครบสามขั้นนี้แล้วจะเห็นว่า config เซิร์ฟเวอร์ไม่ได้เปลี่ยนเลย แต่หน้าเว็บเร็วขึ้นหลายเท่า
ประโยคเดียวที่จำง่าย: ฐานข้อมูลช้าให้หา slow log ก่อน อย่าเพิ่งอัปเกรดเซิร์ฟเวอร์ เพราะ 80% ของ slow query คือ full table scan ที่ไม่ได้ใช้ index เพิ่ม index ให้ถูกตัวได้ผลกว่าอัปเกรด config หนึ่งขั้น
