ASP.NET使用SQL Server FileTable流式播放视频时遇内存不足异常
解决FileTable视频流式传输中的System.OutOfMemoryException异常
看起来你在通过ASPX的VideoHandler.ashx流式传输FileTable中的视频时踩了内存溢出的坑——这个问题我之前帮很多开发者解决过,核心原因是你用了错误的流处理方式,而且没有实现浏览器视频播放必备的分段请求支持。
问题根源分析
你当前用StreamReader来读取视频文件,这本身就不对:StreamReader是为处理文本文件设计的,用它处理视频这种二进制文件会引入编码问题,更关键的是,你的代码逻辑看起来是想把整个视频文件加载到内存里再传输——视频文件动辄几十上百MB,甚至几个GB,一次性加载必然触发OutOfMemoryException,尤其是并发请求多的时候,内存直接就爆了。
完整解决方案
下面是经过验证的修复方案,核心是改用二进制流处理+支持浏览器的Range分段请求,分块传输数据,彻底避免内存溢出。
1. 替换StreamReader为FileStream,实现分段流式传输
把你的Handler代码改成下面这样,我已经加了详细注释:
public void ProcessRequest(HttpContext context) { string fullpath = "你的FileTable视频文件路径"; // 注意:这里要从数据库获取正确的FileTable文件路径 long size, start, end, length; // 用FileStream处理二进制文件,支持共享读取(避免文件被锁) using (FileStream fs = new FileStream(fullpath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { size = fs.Length; string rangeHeader = context.Request.Headers["Range"]; // 处理浏览器的Range请求(视频播放时浏览器会分段请求文件) if (!string.IsNullOrEmpty(rangeHeader)) { // 解析Range头,格式一般是 "bytes=0-1023" string[] rangeParts = rangeHeader.Replace("bytes=", "").Split('-'); start = long.Parse(rangeParts[0]); // 如果Range头只给了起始位置,就默认到文件末尾 end = rangeParts.Length > 1 && !string.IsNullOrEmpty(rangeParts[1]) ? long.Parse(rangeParts[1]) : size - 1; length = end - start + 1; // 返回206 Partial Content状态码,告诉浏览器这是分段数据 context.Response.StatusCode = 206; context.Response.AddHeader("Content-Range", $"bytes {start}-{end}/{size}"); } else { // 没有Range请求时返回整个文件 start = 0; end = size - 1; length = size; context.Response.StatusCode = 200; } // 设置正确的响应头,关键是Content-Type要匹配你的视频格式 context.Response.ContentType = "video/mp4"; // 比如video/avi、video/mov,根据实际文件调整 context.Response.AddHeader("Content-Length", length.ToString()); context.Response.AddHeader("Accept-Ranges", "bytes"); // 告诉浏览器支持分段请求 context.Response.AddHeader("Cache-Control", "public, max-age=86400"); // 可选:添加缓存策略,减轻服务器压力 // 分块读取并传输数据,用4KB缓冲区(可根据服务器性能调整大小) byte[] buffer = new byte[4096]; fs.Seek(start, SeekOrigin.Begin); long bytesSent = 0; while (bytesSent < length) { // 每次读取不超过缓冲区大小,也不超过剩余需要发送的字节数 int bytesRead = fs.Read(buffer, 0, (int)Math.Min(buffer.Length, length - bytesSent)); if (bytesRead == 0) break; context.Response.OutputStream.Write(buffer, 0, bytesRead); context.Response.Flush(); // 立即把数据发送给浏览器,不要在内存里攒着 bytesSent += bytesRead; } } context.Response.End(); }
2. 确保FileTable文件路径的正确性
从FileTable获取文件路径时,一定要用SQL Server提供的GetFileNamespacePath()方法,避免手动拼接路径出错:
SELECT file_stream.GetFileNamespacePath() AS VideoFilePath FROM YourFileTable WHERE YourVideoId = @VideoId; -- 替换成你的表名和ID参数
同时要确保ASP.NET应用程序池的身份账号(比如IIS AppPool\YourPoolName)有访问FileTable对应文件系统目录的权限。
3. 额外优化建议
- 避免一次性加载文件:绝对不要用
File.ReadAllBytes()这类方法把整个视频读入内存,哪怕是小文件也不行,并发起来必炸。 - 调整缓冲区大小:如果服务器性能较好,可以把缓冲区从4KB调到8KB或16KB,提升传输效率;如果服务器内存紧张,保持4KB即可。
- 限制并发请求:如果你的网站访问量很大,可以在IIS里设置应用程序池的并发请求限制,或者在Handler里加简单的限流逻辑,防止内存被占满。
常见坑点排查
- 检查
Content-Type是否正确:如果类型不对,浏览器可能无法识别视频,甚至会下载文件而不是播放。 - 确认文件没有被锁:用
FileShare.ReadWrite打开文件,避免其他进程(比如SQL Server)占用文件导致读取失败。 - 测试Range请求:可以用Postman模拟发送
Range: bytes=0-1023请求,看返回的状态码是不是206,以此验证分段传输是否正常。
内容的提问来源于stack exchange,提问作者Jannik Renner
相关产品推荐
相关产品推荐

