PlusMagi's Blog By Pitt Phunsanit Backend,Database คู่มือการแก้ปัญหา Lock Wait Timeout และปรับปรุงข้อมูล WordPress ขนาดใหญ่ด้วย PHP Script แบบปลอดภัย

คู่มือการแก้ปัญหา Lock Wait Timeout และปรับปรุงข้อมูล WordPress ขนาดใหญ่ด้วย PHP Script แบบปลอดภัย

การจัดการข้อมูลจำนวนมหาศาลในฐานข้อมูล WordPress (ระดับหลักหมื่นถึงหลักแสนเรคคอร์ด) เช่น การรันคำสั่ง SQL เพื่ออัปเดตหรือสลับค่าคอลัมน์ผ่าน Self-Join มักจะนำไปสู่ปัญหาคลาสสิกที่นักพัฒนามือใหม่ต้องเผชิญ นั่นคือ #1205 - Lock wait timeout exceeded หรือปัญหาเว็บล่มเนื่องจากทรัพยากรถูกใช้เกินขีดจำกัด (Resource Exhaustion) เนื่องจากระบบต้องใช้เวลาในการสแกนตารางขนาดใหญ่เกินกว่าที่กำหนด

บทความนี้คือคู่มือสำหรับ Senior Developer ที่ต้องการรู้วิธีการปรับปรุงข้อมูลขนาดใหญ่ใน PostgreSQL อย่างปลอดภัย โดยเฉพาะการหลีกเลี่ยงกับดักของคำสั่งแบ่งหน้าแบบดั้งเดิม

⚠️ ข้อผิดพลาดของ LIMIT + OFFSET ในการประมวลผลข้อมูลขนาดใหญ่

นักพัฒนาส่วนใหญ่มักคุ้นเคยกับการใช้คำสั่งแบ่งหน้า (Pagination) ในรูปแบบ LIMIT 2000 OFFSET 2000 แต่เมื่อนำมาใช้กับตารางขนาดใหญ่ที่มีเรคคอร์ดหลักหมื่นขึ้นไป วิธีนี้จะส่งผลเสียร้ายแรง 2 ประการ:

  1. ประสิทธิภาพลดลงแบบทวีคูณ (Exponential Degradation): เมื่อค่า OFFSET สูงขึ้นมาก (เช่น OFFSET 50000) PostgreSQL จะต้องอ่านและนับข้อมูลตั้งแต่แถวแรกสุดจนถึงเรคคอร์ดที่ 50,000 ก่อนจะตัดทิ้งตามจำนวน LIMIT ทำให้การทำงานในแต่ละรอบช้าลงอย่างมากจนเกิด Timeout
  2. ความเสี่ยงด้านความสมบูรณ์ของข้อมูล (Data Integrity Risk): หากตารางมีการเปลี่ยนแปลงข้อมูล (INSERT, DELETE, UPDATE) ระหว่างที่สคริปต์กำลังรันอยู่ การใช้ลำดับแถวแบบ OFFSET อาจทำให้สคริปต์ข้ามเรคคอร์ดที่ถูกแก้ไขไป หรือดึงข้อมูลเดิมกลับมาทำซ้ำ ทำให้ข้อมูลเสียหายได้

✅ แนวทางแก้ไข: การใช้ Primary Key Range-Based Batching

วิธีที่ถูกต้องและปลอดภัยที่สุดในการประมวลผลข้อมูลขนาดใหญ่คือการใช้หลักการ Range-Based Batching โดยอ้างอิงจากคอลัมน์ที่มี Index ที่เชื่อถือได้ เช่น Primary Key (id) หรือ Timestamp ที่มีการเรียงลำดับเสมอ การทำเช่นนี้จะช่วยให้ PostgreSQL สามารถกระโดดไปยังช่วงข้อมูลที่ต้องการได้ทันที โดยไม่ต้องสแกนข้อมูลตั้งแต่ต้นตาราง

💡 ตัวอย่างการเขียน PostgreSQL Script อย่างปลอดภัย

แทนที่จะใช้ OFFSET เราจะใช้ WHERE id > [Last_ID] LIMIT [Batch_Size] เพื่อให้สคริปต์ทำงานเป็นรอบ (Iteration) โดยการส่งต่อ ID ล่าสุดที่ประมวลผลไปแล้วในแต่ละรอบ

SQL

-- กำหนดขนาด Batch ที่เหมาะสม (เช่น 5000 - 10000 เรคคอร์ด) เพื่อลด Lock Time

-- รอบที่ 1: ดึงข้อมูลที่ ID มากกว่า 0 และมีจำนวน 5000 เรคคอร์ด
SELECT * FROM wordpress_posts WHERE post_id > 0 ORDER BY post_id ASC LIMIT 5000;

-- สมมติว่าเรคคอร์ดสุดท้ายที่ได้คือ post_id = 50000

-- รอบที่ 2: ดึงข้อมูลถัดไป
SELECT * FROM wordpress_posts WHERE post_id > 50000 ORDER BY post_id ASC LIMIT 5000;

-- ทำซ้ำกระบวนการนี้ใน PHP Loop จนกว่าจะไม่มีเรคคอร์ดถูกดึงมา (No results returned)

🛠️ การป้องกัน Lock Wait Timeout ด้วย Transaction Management

เพื่อให้การทำงานเสถียรและป้องกันการเกิด Lock Wait Timeout ในระดับแอปพลิเคชัน (PHP Script) ควรจัดการ Transaction อย่างระมัดระวัง:

  1. Commit อย่างสม่ำเสมอ: ห้ามรันคำสั่ง UPDATE หรือ INSERT จำนวนมากใน Transaction เดียวกัน ควร Commit ทุกๆ Batch ที่ประมวลผลเสร็จสิ้น (เช่น ทุก 5,000 เรคคอร์ด) เพื่อปล่อย Lock ให้กับระบบ
  2. ใช้ Index เสมอ: ตรวจสอบให้แน่ใจว่าคอลัมน์ที่ใช้ในการกรอง (WHERE) หรือเรียงลำดับ (ORDER BY) นั้นมีการสร้าง Index อย่างเหมาะสม
  3. การจัดการทรัพยากร: หากเป็นไปได้ ควรตั้งค่า SET lock_timeout = '5s'; ในการเชื่อมต่อเพื่อกำหนดระยะเวลาการรอ Lock ที่ชัดเจน

✨ บทสรุป

การจัดการข้อมูลขนาดใหญ่ใน PostgreSQL ไม่ได้อยู่ที่การใช้คำสั่งที่ดูซับซ้อน แต่อยู่ที่การเลือกวิธีการที่เหมาะสม Range-Based Batching โดยใช้ Primary Key หรือ Timestamp เป็นตัวกำหนดช่วงข้อมูล (Cursor) คือหัวใจสำคัญในการหลีกเลี่ยงปัญหา Lock Wait Timeout และทำให้สคริปต์ทำงานได้อย่างมีประสิทธิภาพและเสถียรภาพสูงสุด