หมวดหมู่: MariaDB

เทคนิคแทรก Post ID ที่หายไปใน WordPress ด้วย CTE Recursive ในเสี้ยววินาทีเทคนิคแทรก Post ID ที่หายไปใน WordPress ด้วย CTE Recursive ในเสี้ยววินาที

สำหรับคนที่ดูแลฐานข้อมูล WordPress มาอย่างยาวนาน ปัญหาเรื่อง ID Gap หรือตัวเลข ID ในตาราง wp_posts กระโดดข้าม เป็นเรื่องที่พบได้บ่อยมาก ไม่ว่าจะเกิดจากการลบโพสต์ทิ้ง, การสร้าง auto-draft ซ้ำซ้อน, หรือการทดสอบระบบจนค่า AUTO_INCREMENT พุ่งนำหน้า ID จริงไปไกล

บทความนี้จะพามาดูเทคนิคการใช้ Common Table Expressions (CTE) แบบ Recursive เพื่อสร้างตัวเลขรันตั้งแต่ 1 จนถึงค่าก่อน Auto Increment และดึงเฉพาะช่องว่างที่ยังไม่มีข้อมูลมาทำ Placeholder Draft รวดเดียวเกือบ 12,000 แถว ในเวลาไม่ถึงครึ่งวินาที


ทำไมต้องเติม Gap ให้เต็ม?

  • จองสิทธิ์ ID ไว้ใช้งาน: บางระบบต้องการแมป URL แบบสั้น (เช่น /?p=123) ให้มีสถานะเป็น Draft ล่วงหน้า เพื่อป้องกันไม่ให้ระบบไปหยิบ ID ตัวเลขสวย ๆ ไปใช้กับ revision หรือ attachment อื่น
  • ความต่อเนื่องของข้อมูล: ลดความกระจัดกระจายของลำดับ ID ก่อนที่จะเริ่มรันข้อมูลชุดใหม่
  • ความเร็วระดับ Native SQL: การรันผ่าน PHP loop ทั่วไปเพื่อสร้าง 10,000+ โพสต์อาจใช้เวลานานและติด timeout แต่การใช้ CTE สามารถจบงานได้ในระดับ millisecond

โครงสร้างคำสั่ง SQL

หลักการทำงานคือการให้ CTE วนลูปสร้างตัวเลข n เริ่มต้นที่ 1 ไปจนถึง AUTO_INCREMENT - 1 จากนั้นใช้ LEFT JOIN ตรวจหาแถวที่ ID IS NULL เพื่อแทรกเฉพาะ ID ที่ยังว่างอยู่เท่านั้น

-- ขยายขีดจำกัดการวนซ้ำสำหรับ MariaDB
-- SET SESSION cte_max_recursion_depth = 100000; -- สำหรับ MySQL 8.0+
SET SESSION max_recursive_iterations = 100000;

INSERT INTO `wp_posts` (
  `ID`,
  `post_author`,
  `post_date`,
  `post_date_gmt`,
  `post_content`,
  `post_title`,
  `post_excerpt`,
  `post_status`,
  `comment_status`,
  `ping_status`,
  `post_password`,
  `post_name`,
  `to_ping`,
  `pinged`,
  `post_modified`,
  `post_modified_gmt`,
  `post_content_filtered`,
  `post_parent`,
  `guid`,
  `menu_order`,
  `post_type`,
  `post_mime_type`,
  `comment_count`
)
WITH RECURSIVE id_sequence (n, max_id) AS (
  SELECT
    1 AS n,
    (
      SELECT `AUTO_INCREMENT` - 1
      FROM information_schema.TABLES
      WHERE TABLE_SCHEMA = DATABASE()
        AND TABLE_NAME = 'wp_posts'
    ) AS max_id
  UNION ALL
  SELECT
    n + 1,
    max_id
  FROM id_sequence
  WHERE n < max_id
)
SELECT
  s.n,
  1,
  '1982-08-05 07:00:00',
  '1982-08-05 07:00:00',
  '',
  CAST(s.n AS CHAR),
  '',
  'draft',
  'closed',
  'closed',
  '',
  CAST(s.n AS CHAR),
  '',
  '',
  '1982-08-05 07:00:00',
  '1982-08-05 07:00:00',
  '',
  0,
  CONCAT('https://pitt.plusmagi.com/?p=', s.n),
  0,
  'post',
  '',
  0
FROM id_sequence AS s
LEFT JOIN `wp_posts` AS t ON s.n = t.ID
WHERE t.ID IS NULL;

ผลลัพธ์และประสิทธิภาพ

จากการทดสอบใช้งานจริงบนฐานข้อมูล MariaDB คำสั่งสามารถประมวลผลและแทรกแถวข้อมูลได้รวดเร็วอย่างไม่น่าเชื่อ

SELECT s.n AS missing_id
FROM (
  SELECT 1 AS n UNION ALL SELECT 2 -- หรือ query CTE สั้น ๆ
) s
LEFT JOIN `wp_posts` t ON s.n = t.ID
WHERE t.ID IS NULL;

11,917 rows inserted. (Query took 0.4613 seconds.)

การแทรกข้อมูลเกือบ 12,000 แถว ใช้เวลาไปเพียง 0.46 วินาที เท่านั้น โดยได้ผลลัพธ์เป็น Post Placeholder ที่มีสถานะ draft พร้อมใช้งาน โดยไม่รบกวนข้อมูลโพสต์เดิมที่มีอยู่ และยังคงรักษาเลข ID ตามลำดับที่ถูกต้องสมบูรณ์


อ่านเพิ่มเติม