將圖片二進制數據直接存入 MySQL BLOB/MEDIUMBLOB/LONGBLOB 字段,可實現「刪除記錄即刪除文件」的原子性,避免文件系統與數據庫狀態不一致。但會導致數據庫體積膨脹、備份還原變慢、無法利用 CDN/靜態服務器加速。本文提供基於 PDO 的完整實現,並詳細分析利弊與適用場景。
CREATE TABLE `upload` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`type` varchar(20) NOT NULL COMMENT 'MIME 類型,如 image/jpeg',
`data` mediumblob NOT NULL COMMENT '最大 16MB,大圖請用 LONGBLOB',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
<!DOCTYPE html>
<html><head><title>圖片上傳入庫</title></head><body>
<form action="post.php" method="post" enctype="multipart/form-data">
<input type="file" name="file" id="file" accept="image/*" required>
<input type="submit" value="上傳">
</form>
</body></html>
<?php
// post.php
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
header('Location: index.html');
exit;
}
if (!isset($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) {
$errMsg = $_FILES['file']['error'] ?? '未知錯誤';
die("上傳失敗: $errMsg");
}
$file = $_FILES['file'];
$type = $file['type']; // 如 image/png
$size = $file['size'];
$tmp = $file['tmp_name'];
// 安全校驗:僅允許圖片 MIME
$allowed = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
if (!in_array($type, $allowed, true)) {
die('僅支持 JPEG/PNG/GIF/WebP 格式');
}
// 讀取二進制並轉十六進制字符串(PDO 參數綁定更推薦,見下文)
$fp = fopen($tmp, 'rb');
$bin = fread($fp, $size);
fclose($fp);
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
try {
$pdo = new PDO($dsn, 'root', 'root', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_EMULATE_PREPARES => false,
]);
// 方案 A:預處理綁定二進制流(推薦,避免十六進制轉換開銷)
$stmt = $pdo->prepare("INSERT INTO `upload` (`type`, `data`) VALUES (?, ?)");
$stmt->bindParam(1, $type, PDO::PARAM_STR);
$stmt->bindParam(2, $bin, PDO::PARAM_LOB); // 關鍵:PARAM_LOB 直接流式寫入
$stmt->execute();
$id = $pdo->lastInsertId();
echo "上傳成功!<a href='view.php?id=$id'>查看圖片</a>";
} catch (PDOException $e) {
error_log("上傳入庫失敗: " . $e->getMessage());
die('服務器內部錯誤');
}
<?php
// view.php
$id = $_GET['id'] ?? '';
if (!ctype_digit($id)) {
http_response_code(400);
die('非法 ID');
}
$dsn = 'mysql:host=localhost;dbname=test;charset=utf8mb4';
try {
$pdo = new PDO($dsn, 'root', 'root', [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]);
$stmt = $pdo->prepare("SELECT `type`, `data` FROM `upload` WHERE `id` = ?");
$stmt->execute([$id]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
http_response_code(404);
die('圖片不存在');
}
// 關鍵:設置正確 Content-Type,瀏覽器才能正確渲染
header("Content-Type: {$row['type']}");
// 可選:緩存控制
header('Cache-Control: public, max-age=31536000');
header('Content-Length: ' . strlen($row['data']));
echo $row['data'];
} catch (PDOException $e) {
error_log("讀取圖片失敗: " . $e->getMessage());
http_response_code(500);
die('服務器內部錯誤');
}
| 點 | 說明 |
|---|---|
PDO::PARAM_LOB | 綁定參數時指定為大對象,PDO 會以流方式傳輸,避免 bin2hex 雙倍內存佔用與轉換開銷。 |
MEDIUMBLOB 容量 | 16 MB。超過請用 LONGBLOB (4 GB),並調整 max_allowed_packet。 |
| MIME 類型存儲 | 必須存儲,否則瀏覽器無法識別圖片格式。上傳時以 $_FILES['file']['type'] 為準,但建議服務端用 finfo_file 二次驗證。 |
輸出 Content-Length | 允許瀏覽器顯示下載進度、支持範圍請求(斷點續傳/視頻拖拽)。 |
| SQL 注入防護 | 全程使用預處理語句,徹底杜絕注入風險。 |
| 維度 | 存入數據庫 (BLOB) | 存文件系統 + 路徑入庫 |
|---|---|---|
| 數據一致性 | ✅ 事務原子性,刪除記錄 = 刪除文件 | ⚠️ 需分布式事務或補償機制保證一致性 |
| 備份/遷移 | ❌ 單庫巨大,mysqldump 慢,還原久 | ✅ 文件獨立備份,數據庫輕量 |
| 讀取性能 | ❌ 經過 PHP 處理,佔用內存,無法 CDN | ✅ Nginx 直接服務靜態文件,可 CDN 加速 |
| 併發連接 | ❌ 佔用數據庫連接池 | ✅ 靜態資源不佔用應用/數據庫連接 |
| 適用場景 | 小文件(<1MB)、強一致性要求高、文件數量可控(<10萬)、內部系統 | 大文件、高併發、公開訪問、需 CDN、長期歸檔 |
max_allowed_packet。BLOB 存儲是「以運維成本換取數據一致性」的妥協方案。中小型內部系統、證照/簽名等小文件、強事務場景可採用;對外公開的圖片/視頻服務,**強烈建議文件系統 + 對象存儲 + CDN** 架構。技術選型無銀彈,根據業務約束在一致性、性能、成本間做權衡才是正道。