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

PHP 將圖片存入 MySQL 數據庫的多種方案與性能對比

     編輯:PHP 相關知識

前言:為什麼通常不建議將圖片存入數據庫?

將二進制圖片直接存入 MySQL(BLOB/MEDIUMBLOB/LONGBLOB)雖然技術可行,但存在明顯缺點:

  • 數據庫體積膨脹,備份/恢復/複製耗時大幅增加。
  • 查詢緩衝池被大對象佔用,降低熱點數據命中率。
  • 無法直接利用 CDN、瀏覽器緩存、Web 服務器靜態文件優化。
  • 應用層需額外處理流式輸出,增加 CPU/內存開銷。

最佳實踐:圖片存文件系統/對象存儲(OSS/S3),數據庫僅存路徑/URL/元數據。本文僅為技術探討與特殊場景(如強合規要求、小圖標嵌入)提供方案。

核心難點:二進制數據安全寫入 SQL

直接拼接二進制字符串進 SQL 會導致語法錯誤(含單引號、NULL、控制字符等)。需對數據進行「編碼/轉義」後再插入,讀取時「解碼」。常見方案對比:

方案原理存儲體積讀取處理優點缺點
addslashes()轉義單引號、雙引號、反斜線、NULL原大小直接輸出代碼簡單不完整,某些字節仍導致語法錯誤
base64_encode()二進制 → ASCII 字符(A-Z,a-z,0-9,+,/)~133% 原大小base64_decode()純文本安全,無需轉義體積增大 33%,CPU 開銷大
bin2hex()二進制 → 十六進制字符串 (0-9,a-f)200% 原大小UNHEX() 或 hex2bin()MySQL 可直接用 0x 前綴插入,無需轉義體積翻倍,存儲/傳輸成本高
PMA_sqlAddslashes()phpMyAdmin 內部函數,完整轉義原大小直接輸出體積不變,兼容性好需複製函數代碼,維護成本
PDO 參數綁定預處理語句,二進制參數單獨傳輸原大小直接輸出最安全、最高效、體積不變需 PDO 支援,舊 mysql_* 不適用

推薦方案:PDO 參數綁定(最佳實踐)

<?php
$pdo = new PDO("mysql:host=127.0.0.1;dbname=test;charset=utf8mb4", "root", "pass", [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);

// 插入
$imgPath = "photo.jpg";
$imgData = file_get_contents($imgPath); // 獲取二進制內容
$stmt = $pdo->prepare("INSERT INTO `images` (`name`, `type`, `data`) VALUES (?, ?, ?)");
$stmt->bindParam(1, $imgPath, PDO::PARAM_STR);
$stmt->bindParam(2, mime_content_type($imgPath), PDO::PARAM_STR);
$stmt->bindParam(3, $imgData, PDO::PARAM_LOB); // 關鍵:PARAM_LOB 處理大對象
$stmt->execute();
echo "插入成功,ID: " . $pdo->lastInsertId();

// 讀取並輸出
$stmt = $pdo->prepare("SELECT `type`, `data` FROM `images` WHERE `id` = ?");
$stmt->execute([1]);
$stmt->bindColumn(1, $type);
$stmt->bindColumn(2, $data, PDO::PARAM_LOB);
$stmt->fetch(PDO::FETCH_BOUND);
header("Content-Type: $type");
echo $data;
?>

備選方案:bin2hex + 0x 前綴(無 PDO 環境)

<?php
$mysqli = new mysqli("127.0.0.1", "root", "pass", "test");
$imgData = file_get_contents("photo.jpg");
$hexData = bin2hex($imgData); // 二進制轉十六進制字符串
$sql = "INSERT INTO `images` (`data`) VALUES (0x$hexData)"; // 0x 前綴告訴 MySQL 這是十六進制
$mysqli->query($sql);

// 讀取
$res = $mysqli->query("SELECT `data` FROM `images` WHERE `id` = 1");
$row = $res->fetch_assoc();
header("Content-Type: image/jpeg");
echo hex2bin($row['data']); // PHP 5.4+ hex2bin()
?>

phpMyAdmin 同款:PMA_sqlAddslashes 完整實現

若必須用 mysqli/mysql 老擴展且不想引入 PDO,可直接移植 phpMyAdmin 的轉義函數:

function PMA_sqlAddslashes($str, $is_like = false, $crlf = false, $php_code = false) {
    if ($is_like) {
        $str = str_replace('\\', '\\\\', $str);
    } else {
        $str = str_replace('\\', '\\\\', $str); // 先轉義反斜線
    }
    if ($crlf) {
        $str = str_replace("\n", '\\n', $str);
        $str = str_replace("\r", '\\r', $str);
        $str = str_replace("\t", '\\t', $str);
    }
    if ($php_code) {
        $str = str_replace("'", "\\'", $str);
    } else {
        $str = str_replace("'", "''", $str); // SQL 標準:單引號轉兩個單引號
    }
    return $str;
}

// 使用
$imgData = file_get_contents("photo.jpg");
$safeData = PMA_sqlAddslashes($imgData);
$mysqli->query("INSERT INTO `images` (`data`) VALUES ('$safeData')");

完整上傳與顯示流程示例

前端 upload.html

<form method="post" action="upload.php" enctype="multipart/form-data">
  <input type="hidden" name="MAX_FILE_SIZE" value="2097152"> <!-- 2MB -->
  <input type="file" name="img" accept="image/*" required>
  <button type="submit">上傳存入資料庫</button>
</form>

後端 upload.php (PDO 版)

<?php
$pdo = new PDO("mysql:host=127.0.0.1;dbname=test;charset=utf8mb4", "root", "pass", [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);

if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['img'])) {
    $file = $_FILES['img'];
    if ($file['error'] !== UPLOAD_ERR_OK) die("上傳失敗:錯誤碼 {$file['error']}");
    if ($file['size'] > 2 * 1024 * 1024) die("文件超過 2MB 限制");

    $mime = mime_content_type($file['tmp_name']);
    if (!in_array($mime, ['image/jpeg', 'image/png', 'image/gif', 'image/webp'])) die("僅支援 JPG/PNG/GIF/WebP");

    $data = file_get_contents($file['tmp_name']);
    $stmt = $pdo->prepare("INSERT INTO `images` (`name`, `type`, `data`, `size`) VALUES (?, ?, ?, ?)");
    $stmt->execute([$file['name'], $mime, $data, $file['size']]);
    echo "存入成功,ID: " . $pdo->lastInsertId() . " <a href='view.php?id=" . $pdo->lastInsertId() . "'>查看</a>";
}
?>

顯示 view.php

<?php
$pdo = new PDO("mysql:host=127.0.0.1;dbname=test;charset=utf8mb4", "root", "pass", [
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
]);
$id = (int)($_GET['id'] ?? 0);
$stmt = $pdo->prepare("SELECT `type`, `data` FROM `images` WHERE `id` = ?");
$stmt->execute([$id]);
$stmt->bindColumn(1, $type);
$stmt->bindColumn(2, $data, PDO::PARAM_LOB);
if ($stmt->fetch(PDO::FETCH_BOUND)) {
    header("Content-Type: $type");
    header("Content-Length: " . strlen($data));
    echo $data;
} else {
    http_response_code(404);
    echo "圖片不存在";
}
?>

性能與存儲對比實測(17KB JPEG 圖片)

方案數據庫實際佔用備註
PDO PARAM_LOB17.0 KB原始大小,最高效
PMA_sqlAddslashes17.0 KB原始大小,兼容老擴展
base64_encode22.7 KB+33%
bin2hex + 0x34.0 KB+100%

總結與建議

  1. 首選 PDO + PARAM_LOB:體積最小、最安全、代碼最簡潔,現代 PHP 專案標配。
  2. 次選 PMA_sqlAddslashes:維護舊 mysql_/mysqli 專案時的無痛遷移方案。
  3. 避免 base64/bin2hex:除非異構系統強制要求文本格式,否則純增加成本。
  4. 生產環境強烈建議「文件存儲 + 數據庫存路徑」:結合 OSS/CDN,才是可擴展架構。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved