雖然「檔案存檔案系統、資料庫存路徑」是主流架構,但特定場景下直接存入 BLOB 有優勢:交易一致性 (檔案與資料同進同出)、備份原子性 (單一 dump 即完整)、權限控管 (透過 SQL GRANT 管控存取)。本文示範完整流程:建表、上傳 (store.php)、讀取顯示 (getdata.php)、大檔案調校。
CREATE DATABASE binary_data CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
USE binary_data;
CREATE TABLE binary_data (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
description VARCHAR(255) NOT NULL DEFAULT '',
bin_data LONGBLOB NOT NULL,
filename VARCHAR(255) NOT NULL DEFAULT '',
filesize BIGINT UNSIGNED NOT NULL DEFAULT 0,
filetype VARCHAR(100) NOT NULL DEFAULT '',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
關鍵點:LONGBLOB 最大 4GB;filesize 用 BIGINT 防溢位;加上 created_at 利稽核。
<?php
// store.php
require_once 'db_config.php'; // 含 PDO 連線設定
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['form_data'])) {
$file = $_FILES['form_data'];
$desc = trim($_POST['form_description'] ?? '');
// 基本驗證
if ($file['error'] !== UPLOAD_ERR_OK) {
die('上傳失敗,錯誤碼: ' . $file['error']);
}
if ($file['size'] > 24 * 1024 * 1024) { // 24MB 上限
die('檔案超過 24MB 限制');
}
$pdo = getPDO(); // 回傳 PDO 實例
$pdo->beginTransaction();
try {
$stmt = $pdo->prepare(
"INSERT INTO binary_data (description, bin_data, filename, filesize, filetype)
VALUES (?, ?, ?, ?, ?)"
);
$stmt->bindValue(1, $desc, PDO::PARAM_STR);
$stmt->bindValue(2, file_get_contents($file['tmp_name']), PDO::PARAM_LOB);
$stmt->bindValue(3, $file['name'], PDO::PARAM_STR);
$stmt->bindValue(4, $file['size'], PDO::PARAM_INT);
$stmt->bindValue(5, $file['type'], PDO::PARAM_STR);
$stmt->execute();
$id = $pdo->lastInsertId();
$pdo->commit();
echo "檔案已儲存,資料庫 ID: $id
";
} catch (Exception $e) {
$pdo->rollBack();
die('資料庫錯誤: ' . $e->getMessage());
}
} else {
?>
上傳檔案到 MySQL
<?php
// getdata.php?id=123
require_once 'db_config.php';
$id = filter_input(INPUT_GET, 'id', FILTER_VALIDATE_INT);
if (!$id) { http_response_code(400); die('Invalid ID'); }
$pdo = getPDO();
$stmt = $pdo->prepare("SELECT bin_data, filetype FROM binary_data WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) { http_response_code(404); die('Not Found'); }
header('Content-Type: ' . $row['filetype']);
header('Content-Length: ' . strlen($row['bin_data']));
// 可選:強制下載而非瀏覽器內嵌
// header('Content-Disposition: attachment; filename="download"');
echo $row['bin_data'];
<!-- 直接當圖片來源 -->
<img src="getdata.php?id=3" alt="從資料庫讀取的圖片">
<!-- 提供下載連結 -->
<a href="getdata.php?id=5" download>下載檔案</a>
| 層面 | 設定項目 | 建議值 (24MB 範例) | 位置 |
|---|---|---|---|
| HTML 表單 | MAX_FILE_SIZE | 25165824 | store.php hidden input |
| PHP | upload_max_filesize | 24M | php.ini |
| PHP | post_max_size | 25M (略大於 upload_max) | php.ini |
| PHP | memory_limit | 64M+ (需 > post_max_size) | php.ini |
| MySQL | max_allowed_packet | 32M (略大於最大檔案) | my.cnf [mysqld] 區段 |
| MySQL | innodb_log_file_size | 256M+ (大交易需較大 redo log) | my.cnf [mysqld] 區段 |
重啟生效:修改 php.ini 需重啟 php-fpm/apache;修改 my.cnf 需重啟 mysqld。
innodb_file_per_table、考慮壓縮 (COMPRESSED 行格式)、分離檔案表到獨立表空間ETag/Last-Modified、CDN 快取、Range 請求支援MySQL BLOB 存檔技術成熟,但架構決策應基於檔案大小、存取模式、擴展性需求。小型專案、強一致性需求可直接用;中大型系統請採用「資料庫存元資料、物件儲存存內容」的混合架構。