PlusMagi's Blog By Pitt Phunsanit AI,API,architecture,Backend,computer science,Database,engineering,Uncategorized PHP: การทำ Database Migration แทนการรันคำสั่ง SQL สดบน Server

PHP: การทำ Database Migration แทนการรันคำสั่ง SQL สดบน Server

ในฐานะนักพัฒนา PHP ที่ต้องทำงานกับระบบที่เชื่อมต่อฐานข้อมูลอยู่เสมอ ปัญหาที่เรามักเผชิญคือการจัดการโครงสร้างตาราง (Schema Evolution) เมื่อแอปพลิเคชันของเราเติบโตขึ้น โครงสร้างของฐานข้อมูลก็จะเปลี่ยนแปลงตามไปด้วย ไม่ว่าจะเป็นการเพิ่มคอลัมน์, การเปลี่ยนชนิดข้อมูล, หรือการสร้างความสัมพันธ์ใหม่


🐘 Database Migration คือกระบวนการและระบบที่ช่วยให้เราสามารถบันทึก (version control) และจัดการการเปลี่ยนแปลงโครงสร้างของฐานข้อมูลได้อย่างเป็นระเบียบ มีลำดับขั้นตอน และมีความปลอดภัย ทำให้มั่นใจได้ว่าทุกสภาพแวดล้อม (Local, Staging, Production) จะมี Schema ที่สอดคล้องกันเสมอ


หลักการทำงานของ Migration

  • หลักการ Versioning
    แทนที่เราจะรันคำสั่ง SQL สุ่มๆ บนเครื่อง Server ทำให้มันถูกจัดเก็บเป็นไฟล์ที่ถูกเรียงตามหมายเลขเวอร์ชัน (เช่น 20231027_add_user_email.php) ซึ่งทำให้เกิดลำดับขั้นตอนที่ชัดเจน
  • ระบบควบคุมสถานะ (Migration Tracker)
    ระบบ Migration จะมีตารางพิเศษในฐานข้อมูลเพื่อติดตามว่า เวอร์ชันใดบ้างที่ได้ถูกนำไปใช้แล้ว (Applied Versions) เพื่อป้องกันการรันคำสั่งซ้ำ
  • Execution Flow
    เมื่อต้องการอัปเดต Schema, เครื่องมือจะตรวจสอบดูว่าเวอร์ชันใดที่ยังไม่ได้รัน จากนั้นมันจะดำเนินการคำสั่งเหล่านั้นตามลำดับ (Up) และสามารถย้อนกลับได้ (Down/Rollback)

ทำไม Migration จึงดีกว่าการรัน SQL สด (Direct Execution)?

❌ การรัน SQL สด (Direct Execution)
คุณต้องอาศัยความทรงจำของทีมว่าเคยรันคำสั่งนี้ไปแล้วหรือยัง, หากมีคนรันซ้ำอาจเกิด Error หรือข้อมูลเสียหายได้ง่าย และโค้ดทั้งหมดจะปะปนกัน ทำให้ยากต่อการย้อนกลับ (Rollback) อย่างเป็นระบบ

✅ การใช้ Migration
ทุกการเปลี่ยนแปลงถูกบันทึกเป็น “แผนงาน” (Plan) ที่สามารถทำซ้ำได้อย่างแม่นยำ ผู้พัฒนารายใดก็สามารถรันโค้ดนี้ได้โดยมั่นใจว่าฐานข้อมูลจะอยู่ในสถานะที่ถูกต้องและสอดคล้องกับเวอร์ชันของแอปพลิเคชันเสมอ ไม่ว่าจะทำงานบนเครื่องไหนก็ตาม


🛠️ ตัวอย่างโค้ดและการใช้งานจริง (Conceptual PHP Code)

เนื่องจากการทำ Migration จริงๆ มักใช้ Framework ที่มีระบบจัดการให้แล้ว (เช่น Laravel, Symfony Console) แต่เพื่อแสดงหลักการทำงาน เราจะจำลองแนวคิดของ Migration Class โดยใช้ PDO อย่างปลอดภัย


โครงสร้างไฟล์ Migration

<?php
// migration/20231027_create_user_table.php

class CreateUserTable {
    private $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }

    /** 
     * Method ที่ใช้ในการ "นำไปใช้" (Up) - ทำการเพิ่ม Schema
     */
    public function up() {
        // ใช้ IF NOT EXISTS เพื่อให้เป็น Idempotent
        $sql = "CREATE TABLE IF NOT EXISTS users (
            id INT AUTO_INCREMENT PRIMARY KEY,
            username VARCHAR(100) UNIQUE NOT NULL,
            email VARCHAR(100) NOT NULL
        )";
        // DDL Statement ทั่วไป มักใช้ exec()
        $this->pdo->exec($sql);

        echo "✅ Successfully created 'users' table.\n";
    }

    /** 
     * Method ที่ใช้ในการ "ย้อนกลับ" (Down/Rollback) - ยกเลิกการเปลี่ยนแปลง Schema
     */
    public function down() {
        // ต้องระวัง: การ DROP TABLE จะลบข้อมูลทั้งหมด!
        $sql = "DROP TABLE users";
        $this->pdo->exec($sql);

        echo "⏪ Successfully dropped 'users' table.\n";
    }
}
?>

การจำลอง Runner (ระบบรัน Migration)

<?php
// runner.php 
// [*** สมมติว่ามีการเชื่อมต่อ PDO ที่สำเร็จแล้ว ***]

$migrationFiles = ['migration/20231027_create_user_table.php'];
$pdo = getDatabaseConnection(); // Assume this function returns PDO instance
$schema = new \stdClass(); 

try {
    // 1. โหลดและสร้าง Instance ของ Migration Class
    $migration = new CreateUserTable($pdo);

    // 2. รันการเปลี่ยนแปลง (Up)
    echo "--- Running Migration: Creating Users Table ---\n";
    $migration->up(); 
    
    // 3. บันทึกสถานะว่ารันสำเร็จแล้วในตาราง 'migrations_applied'
    // การบันทึกสถานะนี้คือหัวใจของการควบคุมเวอร์ชัน
    $stmt = $pdo->prepare("INSERT INTO migrations_applied (version, name) VALUES (?, ?)");
    $stmt->execute(['20231027', 'create_user']);

    echo "✅ Migration completed and status recorded.\n";
} catch (\PDOException $e) {
    // 4. หากเกิด Error ให้ยกเลิกทุกอย่าง (Rollback logic is critical)
    $migration->down(); 
    die("🚨 Database Migration Failed: " . $e->getMessage());
}
?>

🛡️ ข้อควรระวังและ Best Practices สำหรับมืออาชีพ

  • 1. การจัดการ Transaction (The Crucial Part)
    ทุก Migration ควรถูกห่อด้วย Database Transaction (START TRANSACTIONCOMMIT/ROLLBACK) เพื่อให้มั่นใจว่าถ้าขั้นตอนใดล้มเหลว ข้อมูลทั้งหมดที่เปลี่ยนแปลงไปก่อนหน้านั้นจะถูกยกเลิกอย่างสมบูรณ์
  • 2. ความปลอดภัยในการรันคำสั่ง SQL (Security Concerns)
    สำหรับ DML Statement (INSERT, UPDATE) ต้องใช้ Prepared Statements เสมอ แต่สำหรับ DDL Statement (CREATE TABLE, ALTER TABLE) ซึ่งเปลี่ยนโครงสร้าง มักจะยากต่อการ Parameterize ดังนั้น นักพัฒนาต้องมั่นใจว่าคำสั่งเหล่านี้มาจาก Source Code ที่เชื่อถือได้เท่านั้น และห้ามรวม Input ของผู้ใช้ภายนอกเข้ามาใน Migration โดยเด็ดขาด
  • 3. Best Practice: Idempotency
    การทำ Migration ควรเป็นแบบ Idempotent หมายความว่า ไม่ว่าจะรันโค้ดนี้กี่ครั้ง ผลลัพธ์สุดท้ายของฐานข้อมูลก็ยังคงเหมือนเดิม (เช่น ใช้ CREATE TABLE IF NOT EXISTS หรือตรวจสอบการมีอยู่ของคอลัมน์ก่อน)
  • 4. อย่าเขียนเองทั้งหมด! (Use Dedicated Tools)
    ในโปรเจกต์จริง ควรใช้เครื่องมือที่พิสูจน์แล้ว เช่น Laravel Migrations, Phinx หรือ Schema Tooling ของ ORM อื่นๆ เพื่อลดความเสี่ยงในการเขียน Runner เอง

🚀 สรุปและการนำไปประยุกต์ใช้งาน

การทำ Database Migration ไม่ใช่แค่ “ฟีเจอร์เสริม” แต่เป็นส่วนสำคัญของกระบวนการพัฒนาซอฟต์แวร์ที่เรียกว่า DevOps มันช่วยให้มั่นใจได้ว่าแอปพลิเคชันของคุณจะทำงานได้อย่างเสถียรและสามารถส่งมอบ (Deploy) ได้อย่างต่อเนื่องไม่ว่าจะอยู่ในสภาพแวดล้อมใดก็ตาม

  • Consistency (ความสม่ำเสมอ)
    รับประกันว่าทุกคนในทีมจะใช้ Schema ของฐานข้อมูลเวอร์ชันเดียวกัน
  • Reproducibility (การทำซ้ำ)
    สามารถสร้างสภาพแวดล้อมการทำงานใหม่ (Development) ได้ด้วยคำสั่งเดียว ทำให้ปัญหา “มันทำงานบนเครื่องฉันนะ” หายไป
  • Safety (ความปลอดภัย)
    การทำ Rollback ที่มีระบบ ช่วยให้แก้ไขข้อผิดพลาดของ Schema ได้อย่างปลอดภัย โดยไม่ทำให้ข้อมูลเสียหายทั้งหมด

เมื่อใดก็ตามที่คุณรู้สึกว่าต้องเขียนคำสั่ง ALTER TABLE ซ้ำ ๆ เพื่อปรับปรุงโครงสร้างฐานข้อมูล นั่นคือสัญญาณที่ชัดเจนว่าคุณควรเริ่มใช้ระบบ Database Migration แล้ว!

Exit mobile version