PlusMagi's Blog By Pitt Phunsanit AI,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