sql server 中 varbinary(max) 应用流式读写:读取用 getstream() 避免内存暴涨,写入用显式 sqlparameter + stream;连接字符串可调 packet size;注意流与 datareader 生命周期绑定;sqlite 无原生流支持,需分块存储或 p/invoke。

SQL Server 中 VARBINARY(MAX) 字段读写大文件容易卡死
直接用 SqlDataReader.GetBytes() 读取几百 MB 的 BLOB,内存暴涨、GC 压力大、甚至 OOM——这不是数据太大,是加载方式错了。SQL Server 默认把整个 VARBINARY(MAX) 拉进内存,哪怕你只想要前 100 字节。
- 改用
SqlDataReader.GetStream(),它返回SqlBytes.Stream,支持流式读取,内存占用恒定在几 KB - 写入时别用
SqlCommand.Parameters.AddWithValue(),它会把整个byte[]加载进内存;改用SqlParameter显式指定SqlDbType.VarBinary+Size = -1,再传Stream或SqlBytes - 连接字符串必须加
Packet Size=8192(默认值),但若文件普遍 >1MB,可尝试Packet Size=32767减少网络往返次数(注意:不是越大越好,TCP 层有 MTU 限制)
.NET 6+ 中用 FileStream 和 SqlDataReader.GetStream() 配合出错
常见报错:InvalidOperationException: Invalid attempt to call GetStream on a closed DataReader。根本原因不是代码顺序写错了,而是 GetStream() 返回的流和 SqlDataReader 强绑定——只要 reader 关闭或移动到下一行,流就失效。
- 必须在
using (var reader = cmd.ExecuteReader())作用域内完成全部流操作,不能把 stream 存到外面再读 - 别用
CopyToAsync()直接丢给后台线程——除非你确保 reader 在整个 copy 过程中保持打开且位置不动(实际极难保证) - 安全做法:用
reader.GetStream().CopyTo(fileStream)同步执行,或改用GetFieldValueAsync<stream>(index)</stream>+AsStream()(.NET 6+),它返回独立缓冲流
SQLite 的 BLOB 字段不支持流式读写,怎么避免内存爆炸
SQLite ADO.NET 驱动(如 Microsoft.Data.Sqlite)不提供类似 GetStream() 的 API,GetValue() 或 GetBytes() 必然全量加载。对 >50MB 文件,这很危险。
- 绕过 ORM,用原生 SQLite C API 的
sqlite3_blob_open()—— 通过Microsoft.Data.Sqlite的SqliteConnection.Handle获取底层句柄,再 P/Invoke 调用(需启用UnmanagedCallersOnly支持) - 更实用的折中:把大文件拆成固定块(如 1MB/chunk),存进带
chunk_index的子表,读取时按需拉取对应 chunk,拼接逻辑放在应用层 - 如果只是偶尔导出,接受短暂卡顿,至少加上
GC.Collect()+GC.WaitForPendingFinalizers()在读完后立即释放大数组(仅限桌面端,别在服务器上滥用)
从数据库 BLOB 写文件时,FileMode.Create 和 FileMode.CreateNew 选哪个
看似是文件系统问题,实则影响并发安全。比如多个线程同时写同一路径,用 Create 会互相覆盖;用 CreateNew 则第二个线程直接抛 IOException。
- 如果目标路径由业务唯一生成(如
Guid.NewGuid().ToString("N") + ".pdf"),用CreateNew更稳妥,能暴露重复写入逻辑缺陷 - 如果路径固定(如配置的临时目录),必须加文件锁:
new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None),否则 Windows 下可能报“文件正被另一进程使用” - 别依赖
File.Exists()+CreateNew组合做判断——存在竞态:检查完存在,写入前已被删,导致失败。直接CreateNew尝试,捕获异常处理更可靠
真正麻烦的从来不是“怎么转”,而是“谁在什么时候持有哪段内存”。BLOB 操作里最易忽略的是流生命周期和数据库连接状态的耦合——一个没关 reader,另一个线程已开始读 stream,这种 bug 往往只在高负载时复现。









