应放弃SELECT INTO DUMPFILE,改用客户端流式拉取:通过SSCursor逐行读BLOB、及时写文件、处理空值、加扩展名、分批查询并设READ COMMITTED隔离级别,导出后异步校验。
MySQL 中用 SELECT INTO DUMPFILE 导出单个 BLOB 失败?
直接用 select ... into dumpfile 只能写入单行单字段,且要求当前用户有 file 权限、目标路径在服务端且 mysql 进程有写权限——实际中几乎不可控,导出大批量图片时会报错 error 1290 (hy000): the mysql server is running with the --secure-file-priv option,根本走不通。
实操建议:
- 放弃
INTO DUMPFILE和INTO OUTFILE,它们不是为批量二进制导出设计的 - 改用客户端侧流式拉取:用 Python/Go/Node.js 连数据库,逐行读
blob字段,立刻写入本地文件,避免内存爆炸 - 务必设置
stream_results=True(PyMySQL/MySQLdb)或等效游标模式,否则整张表 BLOB 全载入内存 - 文件名不能只靠数据库 ID,要加扩展名(如从
mime_type字段推断),否则双击打不开
Python + pymysql 流式导出万级图片的最小可靠写法
核心是控制内存、处理空值、跳过损坏数据,而不是“一次性全查出来再遍历”。
实操建议:
- 用
cursor = conn.cursor(pymysql.cursors.SSCursor)(ServerSide Cursor),不缓存结果集 - 每次
cursor.fetchone()后立刻检查if row[1] is None:,BLOB 字段为空很常见,直接写会报TypeError: a bytes-like object is required - 写文件前先
os.makedirs(os.path.dirname(filepath), exist_ok=True),路径含子目录时不然全失败 - 别用
row[0]这种魔术下标,显式命名字段:cursor.execute("SELECT id, image_data, mime_type FROM assets WHERE ...")
for row in cursor:
img_id, blob_data, mime = row
if not blob_data:
continue
ext = {"image/jpeg": ".jpg", "image/png": ".png"}.get(mime, ".bin")
path = f"backup/images/{img_id}{ext}"
with open(path, "wb") as f:
f.write(blob_data)
导出时 CPU 占用高、IO 阻塞其他查询?
不是代码慢,是 MySQL 默认事务隔离级别(REPEATABLE READ)下,长查询会持有很多行锁或间隙锁,尤其当表没主键或没合适索引时。
实操建议:
- 导出语句末尾加
FOR UPDATE?绝对不要——这会让锁升级,阻塞写操作 - 改用
READ COMMITTED隔离级别:SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED,降低锁粒度 - 分批查,用
WHERE id BETWEEN ? AND ?或基于自增主键的游标分页,每批 500–1000 行,查完一批 commit 一次 - 避开业务高峰执行;如果必须在线导出,给查询加
/*+ MAX_EXECUTION_TIME(30000) */防止拖垮实例
备份后怎么验证图片没损坏?
光看文件大小不为 0 没用,BLOB 截断、编码错位、头部污染都可能导致图片无法渲染但文件看似正常。
实操建议:
- 导出同时记录原始
MD5(如果数据库里存了)或导出后立即计算并存到manifest.json,别依赖文件系统时间戳 - 抽样用
PIL.Image.open()尝试加载,捕获IOError和struct.error(常见于 JPEG 头部损坏) - 对 PNG/JPEG 做轻量头校验:
blob_data[:4] == b"\x89PNG"或blob_data[:2] == b"\xff\xd8",比全量解码快十倍 - 别把所有校验逻辑塞进导出主循环——另起一个线程池异步扫刚写完的文件,否则速度掉一半

















