<?php
// backup.php
set_time_limit(0);
ini_set('memory_limit', '512M');
$dbConfig = [
'host' => 'localhost',
'dbname' => 'my_database',
'user' => 'root',
'pass' => 'password',
'charset' => 'utf8mb4',
];
$outputFile = __DIR__ . '/backup_' . date('Ymd_His') . '.sql';
$batchRows = 500; // 每批 INSERT 行數
try {
$pdo = new PDO(
"mysql:host={$dbConfig['host']};dbname={$dbConfig['dbname']};charset={$dbConfig['charset']}",
$dbConfig['user'],
$dbConfig['pass'],
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::MYSQL_ATTR_USE_BUFFERED_QUERY => false] // 非緩衝查詢,大表節省內存
);
} catch (PDOException $e) {
die('DB 連接失敗:' . $e->getMessage());
}
$fp = fopen($outputFile, 'wb');
if (!$fp) die('無法創建輸出文件');
// 1. 文件頭
$header = "-- MySQL Backup Generated by PHP Script\n";
$header .= "-- Date: " . date('Y-m-d H:i:s') . "\n";
$header .= "-- Database: `{$dbConfig['dbname']}`\n";
$header .= "-- --------------------------------------------------\n\n";
$header .= "SET NAMES {$dbConfig['charset']};\n";
$header .= "SET FOREIGN_KEY_CHECKS = 0;\n";
$header .= "SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';\n";
$header .= "SET time_zone = '+00:00';\n\n";
fwrite($fp, $header);
// 2. 獲取所有表名
$tables = $pdo->query("SHOW FULL TABLES WHERE Table_type = 'BASE TABLE'")->fetchAll(PDO::FETCH_COLUMN);
foreach ($tables as $table) {
echo "Processing: $table ...\n";
// 2.1 表結構
$createStmt = $pdo->query("SHOW CREATE TABLE `{$table}`")->fetch(PDO::FETCH_ASSOC);
$createSql = $createStmt['Create Table'] . ";\n\n";
fwrite($fp, "-- --------------------------------------------------\n");
fwrite($fp, "-- Table structure for `{$table}`\n");
fwrite($fp, "-- --------------------------------------------------\n");
fwrite($fp, "DROP TABLE IF EXISTS `{$table}`;\n");
fwrite($fp, $createSql);
// 2.2 表數據
$countStmt = $pdo->query("SELECT COUNT(*) FROM `{$table}`");
$totalRows = (int)$countStmt->fetchColumn();
if ($totalRows === 0) continue;
fwrite($fp, "-- --------------------------------------------------\n");
fwrite($fp, "-- Records for `{$table}`\n");
fwrite($fp, "-- --------------------------------------------------\n");
// 獲取字段列表(用於顯式指定 INSERT 列,防止表結構變更導致錯誤)
$colsStmt = $pdo->query("SHOW COLUMNS FROM `{$table}`");
$columns = $colsStmt->fetchAll(PDO::FETCH_COLUMN);
$colList = '`' . implode('`,`', $columns) . '`';
$selectSql = "SELECT * FROM `{$table}`";
$stmt = $pdo->query($selectSql);
$batch = [];
$rowCount = 0;
while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
$rowCount++;
// 轉義每個值
$escaped = [];
foreach ($row as $val) {
if ($val === null) {
$escaped[] = 'NULL';
} elseif (is_bool($val)) {
$escaped[] = $val ? 'TRUE' : 'FALSE';
} elseif (is_int($val) || is_float($val)) {
$escaped[] = $val;
} else {
// 字符串:使用 PDO::quote 自動處理引號、反斜線、換行等
$escaped[] = $pdo->quote($val);
}
}
$batch[] = '(' . implode(',', $escaped) . ')';
// 批量寫入
if (count($batch) >= $batchRows) {
fwrite($fp, "INSERT INTO `{$table}` ({$colList}) VALUES\n" . implode(",\n", $batch) . ";\n\n");
$batch = [];
}
}
// 剩餘批次
if ($batch) {
fwrite($fp, "INSERT INTO `{$table}` ({$colList}) VALUES\n" . implode(",\n", $batch) . ";\n\n");
}
echo " -> {$rowCount} rows exported.\n";
}
// 3. 文件尾
$footer = "SET FOREIGN_KEY_CHECKS = 1;\n";
fwrite($fp, $footer);
fclose($fp);
echo "\nBackup completed: $outputFile (" . number_format(filesize($outputFile)) . " bytes)\n";
?>
`PDO::quote()` 根據當前連接字符集、SQL 模式自動處理:
手工 `addslashes()` 只處理反斜線、單雙引號,**無法正確處理多字節字符集(GBK/UTF8MB4)下的邊界字節**,極易導致 SQL 注入或導入錯誤。
默認 PDO 會將整個結果集緩存在 PHP 內存中。大表(百萬行)會導致 OOM。非緩衝模式逐行從 MySQL 服務器拉取,**內存佔用恆定**,但要求:
單條 `INSERT` 執行極慢(每條需解析、事務、索引更新)。批量 `INSERT ... VALUES (...),(...),...` 可將速度提升 **10-50 倍**。本腳本每 500 行刷新一次,平衡內存與性能。
防止表結構變更(增刪列、調整順序)導致導入錯誤或數據錯位。
`SET FOREIGN_KEY_CHECKS = 0/1` 允許任意順序建表導入,避免外鍵依賴順序問題。
// 1. 直接輸出 gz 壓縮流
$fp = gzopen($outputFile . '.gz', 'wb9'); // 9 級壓縮
// 後續 fwrite($fp, ...) 不變
gzclose($fp);
// 2. 分卷(單文件 > 100MB 自動分割)
// 需自行實現:檢查 ftell($fp) > 100MB 時關閉當前、創建 part2.sql...
// 3. 完成後上傳至 S3/OSS/COS
// 使用 AWS SDK / 阿里云 SDK / 騰訊云 SDK
// 建議異步隊列處理,避免阻塞 Web 請求
| 問題 | 原因 | 解決 |
|---|---|---|
| 導出文件過大導入超時 | 單次導入事務過大、索引構建慢 | 導入時加 `--max_allowed_packet=1G`、分表導入、關閉唯一檢查 `SET UNIQUE_CHECKS=0` |
| Emoji/特殊字符亂碼 | 數據庫/表/連接字符集非 utf8mb4 | 全鏈路 utf8mb4:表 `CHARSET=utf8mb4`、連接 `SET NAMES utf8mb4`、PHP `charset=utf8mb4` |
| VIEW/觸發器/存儲過程未導出 | `SHOW TABLES` 只返回基表 | 需額外 `SHOW FULL TABLES WHERE Table_type='VIEW'`、`SHOW TRIGGERS`、`SHOW PROCEDURE STATUS` 並導出 `SHOW CREATE VIEW/TRIGGER/PROCEDURE` |
| 大表導出中途斷開 | PHP 超時、內存限制、MySQL 連接超時 | 設置 `set_time_limit(0)`、`ini_set('memory_limit','1G')`、PDO 非緩衝查詢、MySQL `wait_timeout`/`net_read_timeout` 調大 |