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

PHP 圖片上傳存入 MySQL BLOB 欄位實現:多伺服器檔案共享方案

     編輯:PHP 相關知識

將圖片二進位資料直接儲存於 MySQL BLOB 欄位,可實現多伺服器叢集環境下的檔案共享,免去分散式檔案系統或 NFS 掛載的複雜度。本文完整實現上傳、存儲、讀取、輸出流程,並分析優缺點與適用場景。

資料表設計

CREATE TABLE `images` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
  `name` VARCHAR(255) NOT NULL COMMENT '原始檔名',
  `mime_type` VARCHAR(100) NOT NULL COMMENT 'MIME 類型',
  `size` INT UNSIGNED NOT NULL COMMENT '檔案大小(bytes)',
  `data` MEDIUMBLOB NOT NULL COMMENT '二進位資料',
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  INDEX `idx_mime` (`mime_type`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

BLOB 類型選擇指南

類型最大容量適用場景
TINYBLOB255 B極小圖示、圖標
BLOB64 KB縮圖、小圖片
MEDIUMBLOB16 MB**通用網頁圖片(推薦)**
LONGBLOB4 GB高解析度原圖、文件

⚠️ max_allowed_packet (MySQL 設定) 必須 ≥ 最大檔案大小,預設 4MB/64MB 需調大。

完整實現代碼

1. 資料庫連線 (PDO,支援預處理)

<?php
// db.php
$dsn = 'mysql:host=127.0.0.1;dbname=demo;charset=utf8mb4';
$pdo = new PDO($dsn, 'root', '', [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES => false, // 真實預處理
]);
return $pdo;

2. 上傳處理:upload.php

<?php
require 'db.php';
$pdo = $pdo ?? require 'db.php';

$action = $_GET['action'] ?? 'list';

if ($action === 'upload' && $_SERVER['REQUEST_METHOD'] === 'POST') {
    // 驗證檔案
    if (!isset($_FILES['image']) || $_FILES['image']['error'] !== UPLOAD_ERR_OK) {
        die(json_encode(['success' => false, 'msg' => '檔案上傳失敗']));
    }
    $file = $_FILES['image'];
    
    // MIME 驗證 (防偽裝)
    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mime = finfo_file($finfo, $file['tmp_name']);
    finfo_close($finfo);
    $allowedMimes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
    if (!in_array($mime, $allowedMimes)) {
        die(json_encode(['success' => false, 'msg' => '不支援的圖片格式']));
    }
    
    // 讀取二進位資料
    $data = file_get_contents($file['tmp_name']);
    $size = strlen($data);
    
    // 寫入資料庫
    $stmt = $pdo->prepare(
        "INSERT INTO images (name, mime_type, size, data) VALUES (?, ?, ?, ?)"
    );
    $stmt->execute([
        $file['name'],
        $mime,
        $size,
        $data
    ]);
    
    $id = $pdo->lastInsertId();
    echo json_encode(['success' => true, 'id' => $id, 'msg' => '上傳成功']);
    exit;
}

// 列表頁
$stmt = $pdo->query("SELECT id, name, mime_type, size, created_at FROM images ORDER BY id DESC");
$images = $stmt->fetchAll();
?>
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>圖片上傳至資料庫</title>
    <style>
        .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 16px; }
        .card { border: 1px solid #ddd; border-radius: 8px; padding: 12px; text-align: center; }
        .card img { max-width: 100%; height: 150px; object-fit: contain; }
    </style>
</head>
<body>
    <h1>圖片上傳 (存入 MySQL BLOB)</h1>
    <form method="POST" action="?action=upload" enctype="multipart/form-data">
        <input type="file" name="image" accept="image/*" required>
        <button type="submit">上傳</button>
    </form>
    <div class="grid">
        <?php foreach ($images as $img): ?>
            <div class="card">
                <img src="?action=view&id=" alt="">
                <p><strong></strong></p>
                <p> ·  KB</p>
                <p><small></small></p>
            </div>
        <?php endforeach; ?>
    </div>
</body>
</html>

3. 圖片輸出:view.php (或同檔 action=view)

<?php
require 'db.php';
$pdo = $pdo ?? require 'db.php';

if (($action ?? '') === 'view') {
    $id = (int)($_GET['id'] ?? 0);
    if ($id <= 0) { http_response_code(400); exit; }
    
    $stmt = $pdo->prepare("SELECT mime_type, data FROM images WHERE id = ?");
    $stmt->execute([$id]);
    $row = $stmt->fetch();
    
    if (!$row) { http_response_code(404); exit; }
    
    // 輸出圖片
    header('Content-Type: ' . $row['mime_type']);
    header('Content-Length: ' . strlen($row['data']));
    header('Cache-Control: public, max-age=31536000'); // 1 年快取
    header('ETag: "img-' . $id . '"');
    echo $row['data'];
    exit;
}

關鍵技術細節

1. PDO 預處理自動處理二進位資料

PDO::PARAM_LOB 會自動處理,無需 mysql_escape_string (已棄用)。

2. 大檔案記憶體優化

// 分塊讀取寫入 (避免 file_get_contents 佔用大量記憶體)
$stmt = $pdo->prepare("INSERT INTO images (name, mime_type, size, data) VALUES (?, ?, ?, ?)");
$stmt->bindParam(1, $name);
$stmt->bindParam(2, $mime);
$stmt->bindParam(3, $size);
$stmt->bindParam(4, $data, PDO::PARAM_LOB); // 關鍵:LOB 模式

$fp = fopen($file['tmp_name'], 'rb');
$size = fstat($fp)['size'];
$data = $fp; // PDO 會從資源流讀取
$stmt->execute();
fclose($fp);

3. 輸出效能:ETag + 快取

header('ETag: "img-' . $id . '-' . $row['updated_at'] . '"');
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) && $_SERVER['HTTP_IF_NONE_MATCH'] === $etag) {
    http_response_code(304);
    exit;
}

優缺點深度分析

優點缺點
多伺服器自動共享,無需 NFS/分散式 FS資料庫體積快速膨脹,備份/還原耗時
ACID 事務保證,圖片與業務資料原子性查詢大 BLOB 影響緩衝池效能,建議分表
刪除/更新單一行即可,無孤兒檔案無法直接利用 CDN/瀏覽器快取靜態檔案 URL
權限控制集中在資料庫層不適合大檔案 (>10MB) 或高頻讀寫
遷移簡單:單一 SQL dump複寫延遲大時從庫同步慢

適用場景判斷

  • ✅ **適合**:用戶頭像、身分證掃描、小型文檔、驗證碼、臨時圖片、單體應用、中小型專案
  • ❌ **不適合**:高流量商品圖、用戶相冊、視頻、大檔案、需 CDN 加速、微服務架構

進階優化建議

  1. 分離表:images_meta (id, name, mime, size) + images_data (id, data),查詢列表不掃描 BLOB
  2. 快取層:Nginx proxy_cache 或 Redis 快取熱門圖片,減少 DB 壓力
  3. 異步寫入:上傳先存暫存表/檔案,背景 Worker 寫入 BLOB
  4. 混合儲存:縮圖存 BLOB,原圖存物件儲存 (S3/MinIO/OSS),DB 存路徑
  5. 監控指標:表大小、Innodb_buffer_pool_reads、備份耗時

將圖片存入 MySQL BLOB 是「以空間換簡單」的架構決策。在單體/中小型專案、多節點無共享存儲環境下,是快速實現檔案共享的務實選擇。隨著業務增長,建議平滑遷移至「資料庫存元資料 + 物件儲存存檔案」的混合架構。

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