將圖片、PDF、Word 等二進位檔案直接儲存在資料庫中,優點是備份、同步、權限控管單一化;缺點是資料庫體積膨脹、備份還原耗時、大檔案讀寫效能較檔案系統差。本文完整示範 MySQL BLOB 型態儲存檔案的建表、PHP 上傳存入、讀取輸出流程,並討論大檔案設定調整與替代方案。
CREATE DATABASE `file_storage` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE `file_storage`;
CREATE TABLE `files` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
`description` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '檔案說明',
`filename` VARCHAR(255) NOT NULL COMMENT '原始檔名',
`filesize` INT UNSIGNED NOT NULL COMMENT '檔案大小 (bytes)',
`filetype` VARCHAR(100) NOT NULL COMMENT 'MIME 類型',
`content` LONGBLOB NOT NULL COMMENT '二進位內容',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='檔案儲存表';
LONGBLOB 最大 4GB,足涵蓋大多數文件。若僅存圖片,MEDIUMBLOB (16MB) 通常足夠。filename、filetype、filesize 供下載時重建原始檔案資訊。upload.html<!DOCTYPE html>
<html lang="zh-Hant">
<head><meta charset="UTF-8"><title>上傳檔案到 MySQL</title></head>
<body>
<h2>上傳檔案儲存至資料庫</h2>
<form action="store.php" method="post" enctype="multipart/form-data">
<label>說明:<input type="text" name="description" size="50" required></label><br><br>
<label>選擇檔案:<input type="file" name="userfile" required></label><br><br>
<input type="hidden" name="MAX_FILE_SIZE" value="20971520"> <!-- 20MB 限制,需搭配 php.ini -->
<button type="submit">上傳儲存</button>
</form>
</body>
</html>
store.php (PDO 參數綁定,防 SQL Injection)<?php
// store.php
require_once 'db.php'; // PDO 連線設定
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: upload.html');
exit;
}
$desc = trim($_POST['description'] ?? '');
$file = $_FILES['userfile'] ?? null;
if (!$file || $file['error'] !== UPLOAD_ERR_OK) {
die('上傳失敗:' . ($file['error'] ?? '未選擇檔案'));
}
// 讀取二進位內容
$content = file_get_contents($file['tmp_name']);
if ($content === false) {
die('無法讀取暫存檔');
}
$filename = $file['name'];
$filesize = $file['size'];
$filetype = $file['type'] ?: 'application/octet-stream'; // 瀏覽器提供,非 100% 可信
// 寫入資料庫
$sql = 'INSERT INTO `files` (`description`,`filename`,`filesize`,`filetype`,`content`) VALUES (?,?,?,?,?)';
$stmt = $pdo->prepare($sql);
$stmt->bindParam(1, $desc, PDO::PARAM_STR);
$stmt->bindParam(2, $filename, PDO::PARAM_STR);
$stmt->bindParam(3, $filesize, PDO::PARAM_INT);
$stmt->bindParam(4, $filetype, PDO::PARAM_STR);
$stmt->bindParam(5, $content, PDO::PARAM_LOB); // 關鍵:PARAM_LOB 處理大型二進位
if ($stmt->execute()) {
$id = $pdo->lastInsertId();
echo "上傳成功!檔案 ID: $id
";
echo "檢視連結: get.php?id=$id";
} else {
echo "資料庫寫入失敗: " . implode(', ', $stmt->errorInfo());
}
?>
get.php<?php
// get.php
require_once 'db.php';
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id <= 0) {
http_response_code(400);
exit('無效的 ID');
}
$sql = 'SELECT `filename`,`filesize`,`filetype`,`content` FROM `files` WHERE `id` = ?';
$stmt = $pdo->prepare($sql);
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
http_response_code(404);
exit('找不到檔案');
}
// 輸出標頭
header('Content-Type: ' . $row['filetype']);
header('Content-Length: ' . $row['filesize']);
header('Content-Disposition: inline; filename="' . rawurlencode($row['filename']) . '"'); // inline 讓瀏覽器嘗試直接顯示(圖片/PDF)
// 若強制下載改用: header('Content-Disposition: attachment; filename="'.rawurlencode($row['filename']).'"');
// 輸出二進位內容
echo $row['content'];
?>
若需強制下載,將 Content-Disposition 改為 attachment。
| 層面 | 設定項目 | 建議值 (例: 24MB) | 說明 |
|---|---|---|---|
| HTML 表單 | <input type="hidden" name="MAX_FILE_SIZE"> | 24000000 | 瀏覽器端軟性限制,不可全信 |
| PHP | upload_max_filesize | 24M | php.ini,單次上傳上限 |
| PHP | post_max_size | 32M | 需大於 upload_max_filesize,含表單其他欄位 |
| PHP | memory_limit | 128M | 讀取檔案內容到變數需記憶體,大檔建議改用串流 |
| PHP | max_execution_time | 300 (秒) | 上傳大檔耗時,防止逾時 |
| MySQL | max_allowed_packet | 32M | my.cnf/[mysqld] 區段,單次封包大小,需 > 檔案大小 |
| MySQL | innodb_log_file_size | 64M+ | 大交易寫入 BLOB 需足夠 Redo Log 空間 |
重啟服務:修改 php.ini 需重啟 PHP-FPM/Apache/IIS;修改 my.cnf 需重啟 MySQL。
| 維度 | 資料庫儲存 (BLOB) | 檔案系統 + DB 存路徑 |
|---|---|---|
| 備份/還原 | 單一 mysqldump 完成,但檔案極大、耗時久 | DB 快、檔案需同步備份 (rsync/S3) |
| 複寫/同步 | 主從同步自動包含檔案 | 需額外同步檔案目錄 |
| 權限控管 | 資料庫層級 (GRANT/Row Level Security) | 檔案系統 ACL + 應用層邏輯 |
| 讀取效能 | 大 BLOB 佔用緩衝池、網路傳輸慢 | Web Server 直接送檔 (X-Sendfile) 最快 |
| 資料庫體積 | 快速膨脹,影響維護窗口 | DB 維持精簡 |
| 適用場景 | 小檔 (<1MB)、強一致性需求、文件管理系統 | 大檔、高流量、圖片/影音串流 |
上傳端改用 fopen($tmp, 'rb') + PDO::PARAM_LOB 串流綁定(支援驅動視情況),或分塊寫入。下載端若檔案極大,建議:
fpassthru 輸出。X-Sendfile (Apache) / X-Accel-Redirect (Nginx):PHP 僅驗證權限並回應 X-Sendfile: /path/to/file,Web Server 直接高效傳送檔案,PHP 程序立即釋放記憶體。finfo_file 或 mime_content_type)。INSERT, SELECT on files。rawurlencode,防止注入換行符。MySQL BLOB 儲存檔案在小型專案、文件管理系統、需強一致性備份的場景相當方便。本文提供從建表、上傳、讀取、大檔調整到架構選型的完整指引。隨著系統增長,建議評估「物件儲存 (S3/MinIO) + 資料庫存元資料」架構,兼顧擴展性、效能與成本。