雖然「圖片存檔案系統、資料庫只存路徑」是公認最佳實踐,但特定場景(如單檔案部署、資料庫同步備份、嚴格存取控制)仍需將圖片以 BLOB 形式存入 MySQL。本文實測四種編碼方案,分析優缺點並給出推薦做法。
圖片包含大量特殊位元組(0x00、0x0A、0x5C 等),直接拼接進 SQL 字串會導致語法錯誤:
You have an error in your SQL syntax ... near 'q!...'
| 方案 | PHP 函式 | 資料庫欄位型別 | 儲存大小 | 讀出還原 | 優缺點總評 |
|---|---|---|---|---|---|
| Base64 | base64_encode() | TEXT / MEDIUMTEXT | ~23 KB (+35%) | base64_decode() | 通用性高、可直接用於 Data URI、體積膨脹 33% |
| Hex (16進制) | bin2hex() | TEXT / MEDIUMTEXT | ~34 KB (+100%) | MySQL UNHEX() 或 PHP hex2bin() | 體積倍增、不需解碼即可用 0x... 寫入、除錯易讀 |
| addslashes | addslashes() | BLOB / MEDIUMBLOB | 17 KB (原始) | 無需解碼 | 理論可行,但實測 Windows/某些 MySQL 版本仍報錯、不穩定 |
| PMA_sqlAddslashes (phpMyAdmin 同款) | 自訂函式 | BLOB / MEDIUMBLOB | 17 KB (原始) | 無需解碼 | 推薦 體積最小、相容性最好、為 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;
}
此函式模擬 MySQL 字串字面值跳脫規則,將二進位資料安全包裹在 '...' 內。
<form method="post" action="upimage.php" enctype="multipart/form-data">
<input type="hidden" name="MAX_FILE_SIZE" value="204800">
File: <input type="file" name="imgfile">
<input type="submit" value="OK" name="submitbtn">
</form>
<?php
if (isset($_POST['submitbtn']) && isset($_FILES['imgfile'])) {
$file = $_FILES['imgfile'];
if ($file['error'] === UPLOAD_ERR_OK && is_uploaded_file($file['tmp_name'])) {
$fp = fopen($file['tmp_name'], 'rb');
$binary = fread($fp, $file['size']);
fclose($fp);
// 關鍵:使用 PMA_sqlAddslashes
$safeBin = PMA_sqlAddslashes($binary);
$mysqli = new mysqli('localhost', 'root', '123456', 'test');
$name = $mysqli->real_escape_string($file['name']);
$type = $mysqli->real_escape_string($file['type']);
$sql = "INSERT INTO `images` (`name`, `type`, `image`)
VALUES ('$name', '$type', '$safeBin')";
if ($mysqli->query($sql)) {
echo "插入成功!<a href='disimage.php?id={$mysqli->insert_id}'>顯示圖片</a>";
} else {
echo "插入失敗:" . $mysqli->error;
}
$mysqli->close();
}
}
?>
<?php
$id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
if ($id) {
$mysqli = new mysqli('localhost', 'root', '123456', 'test');
$stmt = $mysqli->prepare('SELECT `type`, `image` FROM `images` WHERE `id` = ?');
$stmt->bind_param('i', $id);
$stmt->execute();
$stmt->bind_result($type, $image);
if ($stmt->fetch()) {
header('Content-Type: ' . $type);
header('Content-Length: ' . strlen($image));
echo $image; // 直接輸出二進位
}
$stmt->close();
$mysqli->close();
}
?>
BLOB (64KB)、中圖 MEDIUMBLOB (16MB)、大圖 LONGBLOB (4GB)。images 表,甚至獨立 Schema。prepare() + bind_param(),二進位資料不經 SQL 解析,最安全。若必須存資料庫,PMA_sqlAddslashes + BLOB/MEDIUMBLOB + Prepared Statement 是體積最小、相容性最好、安全性最高的組合。但請三思:除非有極強理由,否則「檔案系統存圖、資料庫存路徑」仍是架構正解。