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

PHP 圖片存入 MySQL BLOB 實戰:上傳、存儲、讀取完整流程與權衡分析

     編輯:PHP 相關知識

將圖片二進制數據直接存入 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>

後端處理:post.php

<?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('服務器內部錯誤');
}

圖片讀取顯示:view.php

<?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、長期歸檔

優化建議(若必須用 BLOB)

  1. 分表分庫:按用戶/時間分片,控制單表體積。
  2. 異步寫入:上傳先存臨時表/消息隊列,後台 Worker 寫入主表,降低請求延遲。
  3. 讀取緩存:熱門圖片用 Redis 緩存二進制,減輕 DB 壓力。
  4. 限制大小:前後端雙重限制上傳大小(如 < 2MB),防止撐爆 max_allowed_packet。

總結

BLOB 存儲是「以運維成本換取數據一致性」的妥協方案。中小型內部系統、證照/簽名等小文件、強事務場景可採用;對外公開的圖片/視頻服務,**強烈建議文件系統 + 對象存儲 + CDN** 架構。技術選型無銀彈,根據業務約束在一致性、性能、成本間做權衡才是正道。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved