MySQL读大BLOB应先查长度筛选,再流式读取;GridFS需规范设置filename、contentType、metadata;迁移用log表续传;删除后需compact回收空间;跨服务须抽象存储适配层。

MySQL BLOB 字段读取时内存爆掉怎么办
直接 SELECT 大 BLOB 字段(比如几百 MB 的视频或 PDF)会把整块二进制塞进 PHP/Python 进程内存,OOM 是常态。不是 MySQL 慢,是你没绕开它。
- 用
SELECT id, LENGTH(data) as size FROM table先查尺寸,筛掉超限文件(比如 >50MB) - Java 用
ResultSet.getBinaryStream();Python 用cursor.fetchone()[1]配合chunked reading(别一次性.read()) - PHP 的
mysqli::use_result()+mysqli_result::fetch_row()流式读,避免store_result()
MongoDB GridFS 写入前必须处理的三个字段
GridFS 不是“扔进去就完事”,filename、contentType、metadata 这仨不设好,后续查不到、下不了、权限对不上。
-
filename别用原始 MySQL 的id或数字名——加后缀,比如"report_12345.pdf",否则下载时浏览器不会识别类型 -
contentType不能靠扩展名猜,得从BLOB前几个字节用file -b --mime-type或 Python 的python-magic实测,否则前端Content-Type错,PDF 打不开 -
metadata字段里存 MySQL 原记录 ID(如{"mysql_id": 12345}),不然迁移后彻底失联
迁移脚本跑一半挂了怎么续传
GridFS 的 _id 是 ObjectId,但 MySQL 的主键是数字,两者无天然映射。硬靠时间戳或自增 ID 对齐,失败后无法判断哪条漏了。
- 在 MongoDB 新建一张
migration_log集合,每写入一个文件就插一条:{"mysql_id": 12345, "gridfs_id": ObjectId("..."), "status": "done"} - 脚本启动时先查
migration_log中status: "done"的最大mysql_id,然后从下一条继续SELECT ... WHERE id > ? - 别用
try/except吞掉所有异常——至少把失败的mysql_id记进日志文件,人工可查
GridFS 文件删除后磁盘空间不释放?
这是最隐蔽的坑:GridFS 删除只是删了 fs.files 和 fs.chunks 里的文档,MongoDB 不会立刻回收磁盘,db.fs.chunks.totalSize() 可能远大于实际占用。
- 确认删干净:查
db.fs.files.find({"metadata.mysql_id": 12345})和db.fs.chunks.find({"files_id": ObjectId("...")})都为空 - 空间回收要等后台
compact或repairDatabase(仅 WiredTiger 引擎支持在线 compact) - 生产环境别用
mongod --repair,停机太久;优先用db.runCommand({compact: "fs.chunks"}),但注意它会短暂锁表
真正麻烦的是跨服务引用——MySQL 里存着 BLOB ID,应用代码却直接读 MongoDB GridFS,中间没抽象层。一旦 GridFS 路径或权限变,全量改代码。留个 storage_adapter 接口比省事重要得多。










