首页 > 解决方案 > Entity Framework 6 - 处理二进制数据时的内存和性能问题(异步方法)

问题描述

我们有一个旧的遗留系统在工作,它将二进制数据存储在我们的 SQL Server 中。当我们使用使用 EF6 的新 .NET 平台在表中查找数据时,内存会出现峰值,与(同步方法)FindAsync相比,性能很糟糕Find

有谁知道解决此问题的解决方法或方法?我已经向 Microsoft 提出了一个问题,但是我们很着急,因为我们的客户在服务器上遇到 20 分钟的冻结。冻结的原因似乎是内存释放速度不够快,无法让垃圾收集清理内存,以及服务器太忙而无法足够快清理的其他情况。

我已经使用 EF 6 制作了一个快速的 .NET 4.8 项目来重现该错误。

using System;
using System.Data.Entity;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;

namespace PerfEFIssueFramework
{
    public class Item
    {
        public int Id { get; set; }
        public byte[] Data { get; set; }
    }

    public class ItemContext : DbContext
    {
        public DbSet<Item> Items { get; set; }

        public ItemContext() : base(@"Data Source=localhost;Initial Catalog=ItemDb;Integrated Security=False;User ID=user;Password=pw")
        { }
    }

    internal class Program
    {
        private static async Task Main(string[] args)
        {
            Console.WriteLine("Ready to consume a lot of memory with EF.");

            using (var db = new ItemContext())
            {
                db.Database.CreateIfNotExists();

                //insert dummy record
                if (db.Items.ToArray().Length == 0)
                {
                    db.Items.Add(new Item { Data = new byte[20 * 1024 * 1024] });
                    db.Items.Add(new Item { Data = new byte[40 * 1024 * 1024] });
                    db.Items.Add(new Item { Data = new byte[60 * 1024 * 1024] });
                    db.Items.Add(new Item { Data = new byte[80 * 1024 * 1024] });
                    db.Items.Add(new Item { Data = new byte[100 * 1024 * 1024] });
                    await db.SaveChangesAsync();
                }
            }

            // Find
            for (int i = 1; i < 6; i++)
            {
                // Find sync - No performance issues
                using (var db = new ItemContext())
                {
                    var stopwatch = Stopwatch.StartNew();
                    Console.WriteLine("Find sync method doesn't have performance and memory issue");
                    var item = db.Items.Find(i);
                    Console.WriteLine($"Record with id '{item.Id}' was fetched in {stopwatch.ElapsedMilliseconds}ms. Press any key to read again...");
                }

                // Find async - performance issues
                using (var db = new ItemContext())
                {
                    var stopwatch = Stopwatch.StartNew();
                    Console.WriteLine("Reproduce FindAsync performance and memory issue:");
                    var item = await db.Items.FindAsync(i);
                    Console.WriteLine($"Record with id '{item.Id}' was fetched in {stopwatch.ElapsedMilliseconds}ms. Press any key to read again...");
                }
            }

            using (var db = new ItemContext())
            {
                db.Database.Delete();
            }
        }
    }
}

性能问题

对数据库的 Find 和 FindAsync 调用

ID 1 = 20mb
ID 2 = 40mb
ID 3 = 60mb
ID 4 = 80mb
ID 5 = 100mb

我们可以清楚地看到 find 不运行 async 方法,它需要 150 到 350ms,但 async 需要 13000ms 到 280000ms

内存问题

内存问题图片

(我们使用 EF Core 发现了同样的问题)

标签: c#sql-serverentity-framework

解决方案


要将 EF 与具有非常大 blob 的表一起使用,您应该使用Table Splitting,并且仅根据需要获取带有 blob 的实体。

或者,您可以通过删除 byte[] 实体属性并将其替换为 DbContext 上的流式访问方法来避免将 blob 数据加载到内存中,如下所示:

public Stream GetItemData(int Id)
{
    var con = (SqlConnection)Database.Connection;

    if (con.State != System.Data.ConnectionState.Open)
        con.Open();

    var cmd = con.CreateCommand();
    cmd.CommandText = "select data from Items where id = @id";
    cmd.Parameters.Add(new SqlParameter("@id", Id));

    var rdr = cmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess);
    var stream = rdr.GetStream(0);
    return stream;            
}

推荐阅读