为何我的Web API每次请求内存占用都会增加近100MB?
问题:每次请求后Web API内存占用增加近100MB
我开发了一个仅通过GET方法返回数据的Web API,但每次发起请求时,RAM占用都会增加近100MB。如果部署在服务器上接收大量请求,这会引发严重问题。我已经尝试过缓存数据,但没有解决问题。
Program.cs 代码
using Microsoft.Extensions.Hosting.WindowsServices; var builder = WebApplication.CreateBuilder(args); var options = new WebApplicationOptions { Args = args, ContentRootPath = WindowsServiceHelpers.IsWindowsService() ? AppContext.BaseDirectory : default }; builder.Host.UseWindowsService(); builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); app.Urls.Add("http://*:5436"); app.UseAuthorization(); app.MapControllers(); app.Run();
控制器代码
using Npgsql; namespace F360Api.Controllers { [ApiController] [Route("[controller]")] public class PedidoController : ControllerBase { private readonly IConfiguration _config; public PedidoController(IConfiguration configuration) { _config = configuration; } private List<Dictionary<string, object>> ExecuteQuery(string query, NpgsqlParameter[] parameters = null) { List<Dictionary<string, object>> result = new List<Dictionary<string, object>>(); string sqlDataSource = _config.GetConnectionString("F360Api"); using (NpgsqlConnection connection = new NpgsqlConnection(sqlDataSource)) { connection.Open(); using (NpgsqlCommand cmd = new NpgsqlCommand(query, connection)) { if (parameters != null) { cmd.Parameters.AddRange(parameters); } using (NpgsqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { var row = new Dictionary<string, object>(); for (int i = 0; i < reader.FieldCount; i++) { row[reader.GetName(i)] = reader[i]; } result.Add(row); } } } } return result; } [HttpGet] public IActionResult Get() { try { string query = @""; --query here List<Dictionary<string, object>> result = ExecuteQuery(query); return Ok(result); } catch (Exception ex) { return StatusCode(500, $"An error occurred: {ex.Message}"); } } [HttpGet("GetByDate")] public IActionResult GetByDate([FromQuery] DateTime startDate, [FromQuery] DateTime endDate) { try { string query = @""; --query here NpgsqlParameter[] parameters = { new NpgsqlParameter("@startDate", NpgsqlTypes.NpgsqlDbType.Timestamp) { Value = startDate }, new NpgsqlParameter("@endDate", NpgsqlTypes.NpgsqlDbType.Timestamp) { Value = endDate } }; List<Dictionary<string, object>> result = ExecuteQuery(query, parameters); return Ok(result); } catch (Exception ex) { return StatusCode(500, $"An error occurred: {ex.Message}"); } } } }
问题原因及解决方法
1. 一次性加载大量数据到内存
你的ExecuteQuery方法会把查询结果全部加载到List<Dictionary<string, object>>中,如果查询返回的数据集很大(比如几万甚至几十万条记录),每次请求都会在内存中创建大量对象,直接导致内存飙升。
解决方法:
- 分页查询:在PostgreSQL的SQL语句中加入
LIMIT和OFFSET,让客户端分批次获取数据,避免一次性加载所有数据。 - 流式返回:改用
IAsyncEnumerable<T>结合yield return逐步返回数据,减少内存占用。示例代码:
[HttpGet] public async IAsyncEnumerable<Dictionary<string, object>> Get() { string query = @"-- 你的查询语句"; string sqlDataSource = _config.GetConnectionString("F360Api"); using (NpgsqlConnection connection = new NpgsqlConnection(sqlDataSource)) { await connection.OpenAsync(); using (NpgsqlCommand cmd = new NpgsqlCommand(query, connection)) { using (NpgsqlDataReader reader = await cmd.ExecuteReaderAsync()) { while (await reader.ReadAsync()) { var row = new Dictionary<string, object>(); for (int i = 0; i < reader.FieldCount; i++) { row[reader.GetName(i)] = reader[i]; } yield return row; } } } } }
2. 弱类型字典的额外开销
Dictionary<string, object>属于弱类型存储,值类型数据会被装箱,不仅增加内存占用,还会加重GC回收压力。
解决方法:
- 创建强类型实体类(比如
Pedido),映射数据库表字段,用List<Pedido>代替List<Dictionary<string, object>>。示例:
public class Pedido { public int Id { get; set; } public DateTime OrderDate { get; set; } // 其他对应数据库列的字段 }
修改ExecuteQuery方法返回强类型集合:
private List<Pedido> ExecuteQuery(string query, NpgsqlParameter[] parameters = null) { List<Pedido> result = new List<Pedido>(); string sqlDataSource = _config.GetConnectionString("F360Api"); using (NpgsqlConnection connection = new NpgsqlConnection(sqlDataSource)) { connection.Open(); using (NpgsqlCommand cmd = new NpgsqlCommand(query, connection)) { if (parameters != null) cmd.Parameters.AddRange(parameters); using (NpgsqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { result.Add(new Pedido { Id = reader.GetInt32(reader.GetOrdinal("id")), OrderDate = reader.GetDateTime(reader.GetOrdinal("order_date")), // 其他字段映射 }); } } } } return result; }
3. 内存泄漏排查
如果以上优化后内存仍持续上涨,需要进一步排查:
- 使用Visual Studio内存诊断工具或dotMemory分析内存快照,定位未被GC回收的对象。
- 检查是否存在静态集合持有对象引用,导致无法释放。
内容的提问来源于stack exchange,提问作者Pedro William
相关产品推荐
相关产品推荐

