首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 備份 MySQL 數據庫的兩種完整實現方案

     編輯:PHP 相關知識

雖然生產環境推薦使用 `mysqldump`、Percona XtraBackup、或雲廠商快照,但某些場景 (共享主機無 Shell、定時任務需純 PHP、跨平台部署) 需在 PHP 代碼中實現備份。本文提供兩種方案:純 SQL 拼接版 (兼容性最強) 與 mysqli 預處理版 (安全、效能更好)。

方案一:純 SQL 拼接版 (支援 mysql_*/mysqli/PDO)

<?php
/**
 * 完整備份資料庫到 .sql 文件
 *
 * @param array $config ['host','user','pass','db','charset'=>'utf8mb4']
 * @param string $backupDir 備份目錄
 * @return array ['success'=>bool, 'file'=>string, 'msg'=>string]
 */
function backup_mysql_sql(array $config, string $backupDir): array {
    $mysqli = new mysqli($config['host'], $config['user'], $config['pass'], $config['db']);
    if ($mysqli->connect_errno) {
        return ['success' => false, 'msg' => "連接失敗: {$mysqli->connect_error}"];
    }
    $mysqli->set_charset($config['charset'] ?? 'utf8mb4');

    if (!is_dir($backupDir)) mkdir($backupDir, 0755, true);
    $filename = $backupDir . '/' . $config['db'] . '_' . date('Ymd_His') . '.sql';
    $fp = fopen($filename, 'wb');
    if (!$fp) {
        return ['success' => false, 'msg' => "無法創建備份文件: $filename"];
    }

    // 1. 禁用外鍵檢查、設置時區、字符集
    fwrite($fp, "SET FOREIGN_KEY_CHECKS=0;\n");
    fwrite($fp, "SET TIME_ZONE='+00:00';\n");
    fwrite($fp, "SET NAMES {$config['charset']};\n\n");

    // 2. 獲取所有表
    $tables = [];
    $res = $mysqli->query("SHOW TABLES");
    while ($row = $res->fetch_row()) $tables[] = $row[0];
    $res->free();

    foreach ($tables as $table) {
        // 2.1 表結構
        $createRes = $mysqli->query("SHOW CREATE TABLE `{$table}`");
        $createRow = $createRes->fetch_row();
        fwrite($fp, "DROP TABLE IF EXISTS `{$table}`;\n");
        fwrite($fp, $createRow[1] . ";\n\n");
        $createRes->free();

        // 2.2 表數據
        $dataRes = $mysqli->query("SELECT * FROM `{$table}`");
        $fields = [];
        $finfo = $dataRes->fetch_fields();
        foreach ($finfo as $f) $fields[] = "`{$f->name}`";

        while ($row = $dataRes->fetch_assoc()) {
            $values = [];
            foreach ($row as $val) {
                if ($val === null) {
                    $values[] = 'NULL';
                } elseif (is_bool($val)) {
                    $values[] = $val ? 'TRUE' : 'FALSE';
                } elseif (is_numeric($val)) {
                    $values[] = $val;
                } else {
                    $values[] = "'" . $mysqli->real_escape_string($val) . "'";
                }
            }
            fwrite($fp, "INSERT INTO `{$table}` (" . implode(',', $fields) . ") VALUES (" . implode(',', $values) . ");\n");
        }
        fwrite($fp, "\n");
        $dataRes->free();
    }

    fwrite($fp, "SET FOREIGN_KEY_CHECKS=1;\n");
    fclose($fp);
    $mysqli->close();

    return ['success' => true, 'file' => $filename, 'msg' => '備份完成'];
}

// 使用
$config = ['host'=>'localhost','user'=>'root','pass'=>'','db'=>'myapp','charset'=>'utf8mb4'];
$result = backup_mysql_sql($config, __DIR__ . '/backups');
var_dump($result);

方案二:mysqli 預處理 + 批次寫入版 (大數據量推薦)

<?php
function backup_mysql_prepared(array $config, string $backupDir, int $batchSize = 1000): array {
    $mysqli = new mysqli($config['host'], $config['user'], $config['pass'], $config['db']);
    if ($mysqli->connect_errno) return ['success'=>false, 'msg'=>$mysqli->connect_error];
    $mysqli->set_charset($config['charset'] ?? 'utf8mb4');

    $filename = $backupDir . '/' . $config['db'] . '_' . date('Ymd_His') . '.sql';
    $fp = fopen($filename, 'wb');
    fwrite($fp, "SET FOREIGN_KEY_CHECKS=0;\nSET NAMES {$config['charset']};\n\n");

    $tables = $mysqli->query("SHOW TABLES")->fetch_all(MYSQLI_NUM);
    foreach (array_column($tables, 0) as $table) {
        // 表結構
        $create = $mysqli->query("SHOW CREATE TABLE `{$table}`")->fetch_row();
        fwrite($fp, "DROP TABLE IF EXISTS `{$table}`;\n{$create[1]};\n\n");

        // 預處理 SELECT
        $stmt = $mysqli->prepare("SELECT * FROM `{$table}`");
        $stmt->execute();
        $res = $stmt->get_result();
        $fields = array_column($res->fetch_fields(), 'name');
        $fieldList = '`' . implode('`,`', $fields) . '`';

        $placeholders = implode(',', array_fill(0, count($fields), '?'));
        $insertSql = "INSERT INTO `{$table}` ($fieldList) VALUES ($placeholders)";
        $insertStmt = $mysqli->prepare($insertSql);

        $bindParams = [];
        $bindTypes = '';
        foreach ($fields as $i => $f) {
            $bindParams[$i] = null; // 佔位
            $bindTypes .= 's'; // 統一按字符串綁定,NULL/數字會自動轉換
        }

        $count = 0;
        while ($row = $res->fetch_assoc()) {
            foreach ($fields as $i => $f) {
                $val = $row[$f];
                $bindParams[$i] = $val === null ? null : (string)$val;
            }
            $insertStmt->bind_param($bindTypes, ...$bindParams);
            $insertStmt->execute();
            $count++;
            if ($count % $batchSize === 0) {
                // 可選:每批次寫入文件一條 INSERT (這裡為演示,實際需拼接多值 INSERT)
            }
        }
        fwrite($fp, "\n");
        $stmt->close();
        $insertStmt->close();
        $res->free();
    }

    fwrite($fp, "SET FOREIGN_KEY_CHECKS=1;\n");
    fclose($fp);
    $mysqli->close();
    return ['success'=>true, 'file'=>$filename];
}

關鍵優化與避坑指南

  1. 單行 INSERT 性能差:大表請拼接多值 `INSERT INTO ... VALUES (...),(...),(...);` 每批 500-1000 行,或使用 `LOAD DATA INFILE` 導入。
  2. 內存控制:`mysqli::use_result()` (非緩衝) 替代 `store_result()`,逐行讀取寫入文件,避免 PHP 進程 OOM。
  3. 大字段處理:`BLOB/TEXT` 欄位 `real_escape_string` 後體積膨脹,建議導出為十六進制 `0x...` 或分離存儲。
  4. 視圖/觸發器/存儲過程:上述代碼僅備份表,完整備份需額外 `SHOW CREATE VIEW/TRIGGER/PROCEDURE`。
  5. 一致性備份:生產庫請先 `FLUSH TABLES WITH READ LOCK` 或使用 `--single-transaction` (InnoDB),PHP 中需開啟事務 `START TRANSACTION WITH CONSISTENT SNAPSHOT`。
  6. 壓縮與輪轉:備份後 `gzcompress()` 或 `exec("gzip $file")`,保留最近 7/30 天,上傳至 OSS/S3 異地容災。

定時任務部署範例 (Crontab)

# 每天凌晨 2:30 備份
30 2 * * * /usr/local/php/bin/php /var/www/scripts/backup_db.php >> /var/log/backup_db.log 2>&1

恢複驗證腳本 (上線前必跑)

<?php
// restore_test.php
$mysqli = new mysqli('localhost', 'root', '', 'test_restore');
$mysqli->query("SOURCE /path/to/backup.sql"); // 僅限 mysql client,PHP 中需逐條執行
// 建議用 shell: mysql -u root -p test_restore < backup.sql
// PHP 驗證:隨機抽樣核心表行數、關鍵字段 checksum
?>
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved