本文目录导读:

在 PHP 项目中,存储和读取二进制数据(如图片、文件、加密数据等)到数据库,主要涉及三个核心步骤:字段类型选择、数据写入、数据读取与输出。
下面以最常见的 MySQL 数据库为例,分步说明整个流程。
数据库字段类型选择
MySQL 提供了几种专门存储二进制数据的 BLOB(Binary Large Object)类型,根据数据大小选择:
| 类型 | 最大长度 | 适用场景 |
|---|---|---|
TINYBLOB |
255 字节 | 小图标、短签名 |
BLOB |
65,535 字节 (64KB) | 普通图片、小文件 |
MEDIUMBLOB |
16,777,215 字节 (16MB) | 中等文件、PDF |
LONGBLOB |
4,294,967,295 字节 (4GB) | 大文件、视频 |
建表示例(存储用户头像):
CREATE TABLE user_avatar (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
avatar_data MEDIUMBLOB NOT NULL, -- 二进制数据主体
mime_type VARCHAR(50) NOT NULL, -- 推荐额外存储文件类型
file_name VARCHAR(255), -- 原始文件名(可选)
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
强烈建议:额外存储
mime_type(如image/png、application/pdf),方便读取时正确设置 HTTP 响应头。
PHP 写入二进制数据到数据库
核心:使用预处理语句 + bind_param 的 b 类型
<?php
// 假设用户上传了一个文件(来自表单或 API)
$filePath = 'path/to/avatar.png';
$userId = 123;
// 1. 读取文件为二进制字符串
$binaryData = file_get_contents($filePath);
if ($binaryData === false) {
die('文件读取失败');
}
// 2. 获取文件信息
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $filePath);
finfo_close($finfo);
// 3. 连接数据库(使用 PDO 或 mysqli,这里用 PDO 示例)
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'root', 'password');
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// 4. 使用预处理语句插入二进制数据
$sql = "INSERT INTO user_avatar (user_id, avatar_data, mime_type) VALUES (?, ?, ?)";
$stmt = $pdo->prepare($sql);
// 关键:绑定参数时,二进制数据用文件流或直接绑定字符串
// PDO 方法一:直接绑定字符串(适合小于 1MB 的数据)
$stmt->bindParam(1, $userId, PDO::PARAM_INT);
$stmt->bindParam(2, $binaryData, PDO::PARAM_LOB); // 对于二进制数据,使用 PARAM_LOB
$stmt->bindParam(3, $mimeType, PDO::PARAM_STR);
$stmt->execute();
// PDO 方法二:使用文件流(适合大文件,节省内存)
// $fileStream = fopen($filePath, 'rb');
// $stmt->bindParam(2, $fileStream, PDO::PARAM_LOB);
// $stmt->execute();
// fclose($fileStream);
echo "写入成功,ID: " . $pdo->lastInsertId();
?>
如果是 MySQLi 扩展:
$mysqli = new mysqli('localhost', 'root', 'password', 'test');
$stmt = $mysqli->prepare("INSERT INTO user_avatar (user_id, avatar_data, mime_type) VALUES (?, ?, ?)");
$null = null; // 占位
$stmt->bind_param('ibs', $userId, $null, $mimeType);
// 使用 send_long_data 发送大数据
$stmt->send_long_data(1, $binaryData);
$stmt->execute();
PHP 从数据库读取二进制数据并输出
最常用场景:根据用户请求直接输出图片/文件
<?php
// 假设通过 URL 参数获取用户 ID: display_avatar.php?user_id=123
$userId = (int)($_GET['user_id'] ?? 0);
if ($userId <= 0) {
header('HTTP/1.1 400 Bad Request');
exit;
}
$pdo = new PDO('mysql:host=localhost;dbname=test;charset=utf8mb4', 'root', 'password');
$sql = "SELECT avatar_data, mime_type FROM user_avatar WHERE user_id = ? ORDER BY id DESC LIMIT 1";
$stmt = $pdo->prepare($sql);
$stmt->execute([$userId]);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$row) {
header('HTTP/1.1 404 Not Found');
exit;
}
// 设置正确的 Content-Type
header('Content-Type: ' . $row['mime_type']);
// 可选:设置缓存策略
header('Cache-Control: public, max-age=86400');
header('Content-Length: ' . strlen($row['avatar_data']));
// 输出二进制数据
echo $row['avatar_data']; // 直接输出流到浏览器
exit;
?>
访问方式:在 HTML 中 <img src="display_avatar.php?user_id=123">。
读取后保存为文件(需要时)
// 从数据库读取后保存到本地文件 $binaryData = $row['avatar_data']; $outputFile = 'downloaded_avatar.' . pathinfo($row['file_name'], PATHINFO_EXTENSION); file_put_contents($outputFile, $binaryData);
重要注意事项
| 问题 | 说明 | 解决方案 |
|---|---|---|
| 内存占用 | 读取大文件(>100MB)到 PHP 变量可能导致内存溢出 | 使用 PDO::PARAM_LOB + 文件流读写,或考虑 将文件存于文件系统,数据库只存路径 |
| SQL 语句长度限制 | 某些 MySQL 客户端限制 max_allowed_packet(默认 4MB) |
调整 max_allowed_packet 值,或拆分数据 |
| Base64 编码陷阱 | 很多人习惯将二进制数据先 base64_encode 再存为 TEXT | ❌ 错误做法:数据膨胀 33%,查询变慢,索引失效;✅ 应直接存 BLOB |
| 事务处理 | 写入大 BLOB 时建议使用事务或分段写入 | 确保原子性,避免损坏 |
一个更完整的现代建议:文件系统 + 数据库路径
对于大多数 Web 项目,不建议二进制数据直接存入数据库(除极特殊情况),推荐方案:
-- 数据库只存文件路径
CREATE TABLE user_files (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
file_path VARCHAR(500) NOT NULL, -- 存储相对路径,如 /uploads/avatars/123.png
mime_type VARCHAR(50),
file_size BIGINT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
优点:
- 数据库备份体积小,性能好
- 文件可直接由 Nginx/Apache 服务,无需 PHP 处理
- 支持 CDN、分布式存储(S3、OSS 等)
何时仍用数据库存储二进制?
- 数据必须与数据库事务强一致(如加密数据、法律凭证附件)
- 文件很小(< 100KB)且数量巨大
- 需要数据库级别的加密、压缩或列级权限控制
| 操作 | 要点 |
|---|---|
| 建表 | 用 MEDIUMBLOB / LONGBLOB,额外存 mime_type |
| 写入 | 使用预处理语句 bindParam 的 PARAM_LOB,传入二进制字符串或文件流 |
| 读取 | 设置正确的 Content-Type,直接 echo 二进制数据到输出 |
| 性能 | 小文件可直接存;大文件建议存路径,避免 PHP 处理 IO |
如果你的数据是超大文件(>10MB),强烈建议采用 文件系统存储 + 数据库记录路径 的模式,而不是直接存入 BLOB 字段。