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

Why a first request take more time?

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

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();

结果:


如您所见,第一个请求总是需要更多时间。为什么会这样?

我认为 ORM open/close 每个请求的数据库连接,也许不是,EF Core 打开连接 只在第一次 并用于所有下一个请求直到 DbContext 处理掉?

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

我建议您阅读这些关于 EF 执行和性能的主题:

managing ef in a right way

the query plan cache story

第一个请求较慢,因为 Entity Framework 必须从您的模型创建映射视图。这叫做"compiling your model"。当您执行第一个查询时会发生这种情况。

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

所以如果你尝试这样的事情:

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}");

第一个查询将比第二个查询慢很多,即使它 return 没有任何记录。

当您现在从您的上下文中获取另一个实体(假设您的上下文中有一个名为 employees 的数据库集)并对这个数据库集执行第一个查询并在之后对项目执行查询时,您会看到项目查询将 运行 快得多。

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}");

解决此问题的一种方法是在启动期间执行 "fake" 查询,这样第一个用户执行的查询就不会很慢