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

MySQL 儲存二進位檔案 (圖片/文件) 完整教學:從建表、上傳、讀取到效能考量

     編輯:PHP 綜合知識

前言

將圖片、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>

三、PHP 存入程式 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()); } ?>

四、PHP 讀取/下載程式 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。

五、大檔案 (>1MB) 所需調整

層面設定項目建議值 (例: 24MB)說明
HTML 表單<input type="hidden" name="MAX_FILE_SIZE">24000000瀏覽器端軟性限制,不可全信
PHPupload_max_filesize24Mphp.ini,單次上傳上限
PHPpost_max_size32M需大於 upload_max_filesize,含表單其他欄位
PHPmemory_limit128M讀取檔案內容到變數需記憶體,大檔建議改用串流
PHPmax_execution_time300 (秒)上傳大檔耗時,防止逾時
MySQLmax_allowed_packet32Mmy.cnf/[mysqld] 區段,單次封包大小,需 > 檔案大小
MySQLinnodb_log_file_size64M+大交易寫入 BLOB 需足夠 Redo Log 空間

重啟服務:修改 php.ini 需重啟 PHP-FPM/Apache/IIS;修改 my.cnf 需重啟 MySQL。

六、效能與架構考量:資料庫 vs 檔案系統

維度資料庫儲存 (BLOB)檔案系統 + DB 存路徑
備份/還原單一 mysqldump 完成,但檔案極大、耗時久DB 快、檔案需同步備份 (rsync/S3)
複寫/同步主從同步自動包含檔案需額外同步檔案目錄
權限控管資料庫層級 (GRANT/Row Level Security)檔案系統 ACL + 應用層邏輯
讀取效能大 BLOB 佔用緩衝池、網路傳輸慢Web Server 直接送檔 (X-Sendfile) 最快
資料庫體積快速膨脹,影響維護窗口DB 維持精簡
適用場景小檔 (<1MB)、強一致性需求、文件管理系統大檔、高流量、圖片/影音串流

七、進階改善:串流讀寫避免記憶體爆炸

上傳端改用 fopen($tmp, 'rb') + PDO::PARAM_LOB 串流綁定(支援驅動視情況),或分塊寫入。下載端若檔案極大,建議:

  1. 將檔案寫入暫存檔或直接 fpassthru 輸出。
  2. 啟用 X-Sendfile (Apache) / X-Accel-Redirect (Nginx):PHP 僅驗證權限並回應 X-Sendfile: /path/to/file,Web Server 直接高效傳送檔案,PHP 程序立即釋放記憶體。

八、安全性檢查清單

  • 上傳前驗證 MIME 類型與副檔名白名單(finfo_file 或 mime_content_type)。
  • 資料庫帳號最小權限:僅 INSERT, SELECT on files。
  • 下載時驗證使用者權限,防止遍歷下載他人檔案。
  • 檔名輸出標頭時使用 rawurlencode,防止注入換行符。
  • 定期掃描資料庫中惡意腳本內容(雖存 BLOB 不會執行,但下載後可能被誤執行)。

結語

MySQL BLOB 儲存檔案在小型專案、文件管理系統、需強一致性備份的場景相當方便。本文提供從建表、上傳、讀取、大檔調整到架構選型的完整指引。隨著系統增長,建議評估「物件儲存 (S3/MinIO) + 資料庫存元資料」架構,兼顧擴展性、效能與成本。

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