Optimize WordPress Database ให้โหลดเร็วขึ้น

ล้าง overhead ใน database WordPress ให้เว็บโหลดเร็วขึ้น ด้วยวิธีที่ทำได้เองโดยไม่ต้องกลัวข้อมูลหาย

Optimize WordPress Database

ทำไม WordPress Database ถึงช้าลงเมื่อเวลาผ่านไป

WordPress database สะสมข้อมูลที่ไม่จำเป็นมากมายตลอดเวลา ทำให้ queries ช้าลง โดยเฉพาะ wp_options table ที่เป็น bottleneck หลักของหลายเว็บ สาเหตุหลักได้แก่

ขั้นตอนที่ 1 — ใช้ WP-Optimize Plugin

WP-Optimize เป็น plugin ที่ทำ database cleanup ได้ง่ายที่สุด รองรับทั้ง single site และ Multisite ฟรีและมีแผน Pro

  1. ติดตั้ง WP-Optimize จาก WordPress Plugin Directory
  2. ไปที่ WP-Optimize > Database
  3. เลือก cleanup ที่ต้องการ เช่น "Clean all post revisions", "Clean all expired transients", "Remove spam and trashed comments"
  4. คลิก "Run Optimization"
  5. ตั้ง schedule อัตโนมัติให้รันทุกสัปดาห์
Tip: ทำ backup database ก่อนรัน cleanup ครั้งแรกเสมอ แม้ WP-Optimize จะปลอดภัยแต่ก็ควรมี backup ไว้เผื่อฉุกเฉิน

ขั้นตอนที่ 2 — จำกัด Post Revisions

WordPress บันทึก revision ทุกครั้งที่กด save ค่า default คือเก็บไม่จำกัด เพิ่มบรรทัดต่อไปนี้ใน wp-config.php

// จำกัด revision ไว้แค่ 3 ชุดต่อ post
define('WP_POST_REVISIONS', 3);

// หรือปิด revision ทั้งหมด (ไม่แนะนำ)
define('WP_POST_REVISIONS', false);

ลบ revision เก่าที่มีอยู่แล้วด้วย SQL หรือ WP-CLI

# ลบ revision ทั้งหมดผ่าน WP-CLI
wp post delete $(wp post list --post_type='revision' --format=ids) --force

# หรือผ่าน MySQL
DELETE FROM wp_posts WHERE post_type = 'revision';
DELETE FROM wp_postmeta WHERE post_id NOT IN (SELECT ID FROM wp_posts);

ขั้นตอนที่ 3 — ล้าง Expired Transients

Transients คือ temporary cached data ที่ plugin เก็บไว้ใน database เมื่อหมดอายุควรถูกลบออกอัตโนมัติ แต่บ่อยครั้งที่ไม่ถูกลบ ทำให้ wp_options table ใหญ่มาก

# ลบ expired transients ผ่าน WP-CLI
wp transient delete --expired

# ลบทั้งหมด (ระวัง - อาจทำให้ cache หมดและเว็บช้าชั่วคราว)
wp transient delete --all

# ผ่าน MySQL
DELETE FROM wp_options
WHERE option_name LIKE '_transient_%'
AND option_name NOT LIKE '_transient_timeout_%'
AND option_name IN (
  SELECT CONCAT('_transient_', SUBSTRING(option_name, 20))
  FROM wp_options
  WHERE option_name LIKE '_transient_timeout_%'
  AND option_value < UNIX_TIMESTAMP()
);

ขั้นตอนที่ 4 — ตรวจสอบและแก้ไข Autoloaded Options

wp_options ที่มี autoload=yes จะถูก load ทุก page load ซึ่งถ้ามีข้อมูลใหญ่มากจะทำให้เว็บช้าอย่างมีนัยสำคัญ

# ดูขนาด autoloaded data ทั้งหมด
SELECT SUM(LENGTH(option_value)) / 1024 / 1024 AS total_autoload_mb
FROM wp_options
WHERE autoload = 'yes';

# ดู top 20 autoloaded options ที่ใหญ่ที่สุด
SELECT option_name, LENGTH(option_value) / 1024 AS size_kb, autoload
FROM wp_options
WHERE autoload = 'yes'
ORDER BY LENGTH(option_value) DESC
LIMIT 20;

# เปลี่ยน autoload เป็น no สำหรับ option ที่ไม่จำเป็น
UPDATE wp_options SET autoload = 'no'
WHERE option_name = 'plugin_option_name_here';

ขั้นตอนที่ 5 — Optimize Tables ด้วย phpMyAdmin

MySQL tables สะสม fragmentation เมื่อมีการลบข้อมูลบ่อยๆ การรัน OPTIMIZE TABLE จะ reclaim พื้นที่และปรับ index ให้มีประสิทธิภาพขึ้น

  1. เข้า cPanel > phpMyAdmin
  2. เลือก database ของ WordPress
  3. เลือกทุก table (Check All)
  4. จาก dropdown "With selected" เลือก "Optimize table"
  5. รอจนเสร็จ
# ผ่าน command line
mysqlcheck -u username -p --optimize database_name

# Optimize ทุก table พร้อมกัน
mysql -u username -p -e "SELECT CONCAT('OPTIMIZE TABLE ', table_schema, '.', table_name, ';')
FROM information_schema.tables WHERE table_schema = 'your_db_name';" | mysql -u username -p

ขั้นตอนที่ 6 — ล้าง Orphaned Postmeta

เมื่อลบ post, page หรือ custom post type ออกไปแล้ว metadata ที่เกี่ยวข้องมักไม่ถูกลบตาม ทำให้ wp_postmeta โตขึ้นเรื่อยๆ

# หา orphaned postmeta
SELECT pm.meta_id, pm.post_id, pm.meta_key
FROM wp_postmeta pm
LEFT JOIN wp_posts p ON pm.post_id = p.ID
WHERE p.ID IS NULL
LIMIT 20;

# ลบ orphaned postmeta
DELETE pm FROM wp_postmeta pm
LEFT JOIN wp_posts p ON pm.post_id = p.ID
WHERE p.ID IS NULL;

# ล้าง orphaned termmeta
DELETE tm FROM wp_termmeta tm
LEFT JOIN wp_terms t ON tm.term_id = t.term_id
WHERE t.term_id IS NULL;

ตรวจสอบความเร็ว Query และประสิทธิภาพ Database

# ดูขนาดของแต่ละ table
SELECT table_name, ROUND((data_length+index_length)/1024/1024, 2) AS size_mb,
  table_rows, ROUND(data_free/1024/1024, 2) AS overhead_mb
FROM information_schema.tables
WHERE table_schema = 'your_database_name'
ORDER BY (data_length+index_length) DESC;

# ตรวจสอบ slow queries (ถ้าเปิด slow log ไว้)
mysqldumpslow -s t /var/log/mysql/mysql-slow.log | head -20

# ดู query performance ผ่าน WP-CLI
wp eval 'global $wpdb; echo $wpdb->num_queries . " queries";'
Tip: ติดตั้ง Query Monitor plugin เพื่อดูว่า query ไหนช้าที่สุดบนเว็บของคุณ จากนั้นเพิ่ม index ที่เหมาะสมหรือแก้ปัญหาที่ plugin ที่ทำ query นั้น

แก้ปัญหาที่พบบ่อย

หลัง Optimize แล้วเว็บกลับช้าลง

การลบ transient ทั้งหมดทำให้ plugin ต้องสร้าง cache ใหม่ ซึ่งต้องใช้เวลา ปล่อยให้เว็บทำงานไปสักชั่วโมงแล้วจะกลับมาเร็วขึ้น หรือใช้ตัวเลือก "delete expired only" แทน "delete all"

wp_options ยังใหญ่อยู่แม้ clean แล้ว

ใช้ query ดู autoloaded options ขนาดใหญ่ และตรวจสอบว่า plugin ตัวไหนเก็บข้อมูลขนาดใหญ่ไว้ บาง plugin เช่น Revolution Slider หรือ plugin เก่าๆ เก็บข้อมูลขนาดใหญ่มากโดยไม่จำเป็น

OPTIMIZE TABLE ล้มเหลวด้วย Error 1005

InnoDB ไม่รองรับ OPTIMIZE TABLE โดยตรงใน MySQL รุ่นเก่า ให้ใช้ ALTER TABLE tablename ENGINE=InnoDB แทน ซึ่ง rebuild table และ reclaim overhead

Database Cleanup ทำให้ Plugin ทำงานผิดปกติ

Restore จาก backup ที่ทำไว้ก่อน cleanup จากนั้นระบุว่า plugin ตัวไหนได้รับผลกระทบแล้ว exclude option นั้นออกจาก cleanup list


คำถามที่พบบ่อย

ควร Optimize Database บ่อยแค่ไหน

สำหรับเว็บที่อัปเดตเนื้อหาทุกวัน แนะนำทำทุกสัปดาห์ด้วยการตั้ง schedule ใน WP-Optimize เว็บที่อัปเดตไม่บ่อย ทำเดือนละครั้งก็เพียงพอ

InnoDB กับ MyISAM ต่างกันอย่างไร และ WordPress ใช้แบบไหน

WordPress ใช้ InnoDB เป็น default ตั้งแต่เวอร์ชัน 4.2 InnoDB รองรับ transaction, foreign key และมี crash recovery ที่ดีกว่า MyISAM แม้ table บางตัวในเว็บเก่าอาจยังเป็น MyISAM อยู่ ซึ่งควร convert เป็น InnoDB

ลบ Post Revisions แล้ว กลับ restore ได้ไหม

ไม่ได้แล้วครับ เมื่อลบ revision ออกจาก database แล้วจะกู้คืนไม่ได้ยกเว้นมี database backup ก่อนการลบ นี่คือเหตุผลสำคัญที่ต้องทำ backup ก่อน optimize ทุกครั้ง