为什么第一个请求需要更多时间?

时间:2019-03-30 07:49:41

标签: c# sql-server .net-core entity-framework-core ef-core-2.1

今天在一些实验中,我注意到了一件有趣的事情:

var dbContextOptionsBuilder = new DbContextOptionsBuilder<MyContext>();
dbContextOptionsBuilder.UseSqlServer(@"Data Source=LAPTOP-HBBAKRHO\SQLEXPRESS;Initial Catalog=myDb;Integrated Security=True");
var context = new MyContext(dbContextOptionsBuilder.Options);

Stopwatch stopWatch;

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p.Id.Equals(12345));
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p.Id.Equals(12345));
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p.Id.Equals(12345));
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

// CLOSE.
context.Dispose();

结果:

  • AsNoTracking()。SingleOrDefaultAsync,ID:2457
  • AsNoTracking()。SingleOrDefaultAsync,ID:51
  • AsNoTracking()。SingleOrDefaultAsync,ID:29

如您所见,第一个请求始终会花费更多时间。为什么会这样?

我认为每个请求都需要ORM打开/关闭数据库连接,也许不是,并且EF Core打开连接仅是第一次,并将其用于所有下一个请求,直到DbContext处理完毕的?

2 个答案:

答案 0 :(得分:1)

您每次都是从相同的上下文实例通过相同的参数获取数据,因此第二个和第三个get请求将完全不会发送到SQL Server。 EF拥有自己从数据库中加载的所有实体的一级缓存。这就是为什么第二次和第三次要快得多的原因。如果将第二个查询的参数12345更改为其他任何参数,它将比第一个更快,但不会那么快,因为它将向服务器请求数据。

我建议您阅读以下有关EF执行和性能的主题:

managing ef in a right way

the query plan cache story

答案 1 :(得分:0)

第一个请求比较慢,因为Entity Framework必须从您的模型创建映射视图。这称为“编译模型”。当您执行第一个查询时,会发生这种情况。

因此,在您的实验中,即使没有返回任何记录,第一个查询也应该很慢。

因此,如果您尝试这样的操作:

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p == -1); // record does not exist
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p == -2); // record does not exist
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

第一个查询将比第二个查询慢很多,即使它不返回任何记录。

现在您从上下文中获取另一个实体(假设您的上下文中有一个名为employee的dbset)并针对该dbset执行第一个查询,然后对项目执行查询,您将看到项目查询将运行得更快。

stopWatch = Stopwatch.StartNew();
context.Employees.AsNoTracking().SingleOrDefault(e => e == -1); // record does not exist
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");


stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p == -1); // record does not exist
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

stopWatch = Stopwatch.StartNew();
context.Projects.AsNoTracking().SingleOrDefault(p => p == -2); // record does not exist
stopWatch.Stop();
Debug.WriteLine($"AsNoTracking().SingleOrDefaultAsync, by ID: {stopWatch.ElapsedMilliseconds}");

解决此问题的一种方法是在启动过程中执行“伪”查询,以使第一个用户执行的查询不会变慢