第一次调用后,对不同数据库上下文的 EF 查询似乎缓存

Posted

技术标签:

【中文标题】第一次调用后,对不同数据库上下文的 EF 查询似乎缓存【英文标题】:EF queries to different DB contexts seem to cache after the first call 【发布时间】:2021-06-18 21:20:04 【问题描述】:

我有一个包含三个表的数据库,我正在制作一个程序,该程序将选择并需要同时显示所有三个表的结果。

为了加快记录的获取速度,我决定使请求异步,以便它们可以同时发生。 由于您不能对同一个数据库上下文执行多个并发操作,因此我只实例化总共三个上下文,并在每个上下文上启动一个 Linq 查询“ToListAsync()”。

但是发生了一些奇怪的事情(对我来说)——异步版本并不比同步版本快。无论我是使用“.ToListAsync()”异步获得结果还是使用“.ToList()”同步获得结果,它们都需要相同的时间。使用同步版本,第一个操作大约需要 2 秒才能完成,而第二个大约需要 170 毫秒,第三个大约需要 90 毫秒。我想某种执行计划或缓存或建立连接是什么使第一个请求需要很多时间,而其他请求几乎立即完成,但我想知道为什么会发生这种情况以及是否有办法做到这一点快点。 忘了提到异步版本需要相同的时间 - 2 秒。

相关代码:

        int maxRecords = 10;
        var resultsTableOne = dbContext.tableOne.Where(log =>
                                            log.DateUtc >= dateFrom || dateFrom == null)
                                        && (log.DateUtc <= dateTo || dateTo == null))
                                            .OrderByDescending(log => log.Id).Take(maxRecords).ToList();
        var resultsTableTwo = dbContext.tableTwo.Where(log =>
                                            log.DateUtc >= dateFrom || dateFrom == null)
                                        && (log.DateUtc <= dateTo || dateTo == null))
                                            .OrderByDescending(log => log.Id).Take(maxRecords).ToList();
        var resultsTableThree = dbContext.tableThree.Where(log =>
                                            log.DateUtc >= dateFrom || dateFrom == null)
                                        && (log.DateUtc <= dateTo || dateTo == null))
                                            .OrderByDescending(log => log.Id).Take(maxRecords).ToList();

这里简化了选择条件。对于异步版本,“ToList”被替换为“ToListAsync”,它返回一个任务,在此处显示的代码之后调用“GetAwaiter().GetResult()”(并且“dbContext”被替换为三个单独的实例)。

附: 同步版本的结果与一个和三个 DbContext 相同。 IE。无论是在同一个实例上执行所有三个操作还是在自己的实例上执行所有三个操作都需要相同的时间。

【问题讨论】:

你能分享你的代码吗? @smoksnes,哦,是的,抱歉 - 现在已编辑。 In order to speed up the getting of records 为此,修复查询和索引。并发执行不会使服务器的磁盘或 CPU 运行得更快,或者网卡的带宽增加一倍。 为什么查询很慢?多次执行错误的查询会使其变慢而不是变快。如果查询没有合适的索引,如果长寿命事务和连接阻塞它,如果缺少索引导致表级锁,并发查询只会增加阻塞和IO延迟跨度> dateUtc 是否已编入索引?是Id?如果不是,每个查询最终都会扫描(并锁定)整个表以查找匹配的行。log.DateUtc &gt;= dateFrom || dateFrom == null ???这是一个错误,导致执行计划错误。如果您不想使用 dateFrom 过滤器,只需省略不要使用 Where 子句。仅此一项就可以强制所有查询扫描整个表,而不是使用DateUtc 上的任何索引。这种包罗万象的查询的原因是explained here @user14092802 如果您不能使用索引,就无法加快速度。故事结局。第一个查询总是会比较慢,因为它会扫描整个表。后续查询总是会更快,因为数据被缓存直到服务器清除缓存。这是查询和数据库设计的问题,而不是服务器的问题。并发执行不会使原本很慢的查询变得更快 【参考方案1】:

为了加快记录的获取速度,我决定让请求异步,以便它们可以同时发生。

这不是async/await 模式的作用。您似乎想要做的是并行化,您可以使用async 方法及其关联的Task,但这需要3 个不同的DbContext 实例。作为与您拥有的代码相关的一个非常简单的示例:

using (var context1 = new AppDbContext())

    using (var context2 = new AppDbContext())
    
        using (var context3 = new AppDbContext())
        
            var table1Task = context1.tableOne
                .Where(log =>
                    log.DateUtc >= dateFrom || dateFrom == null)
                    && (log.DateUtc <= dateTo || dateTo == null))
                .OrderByDescending(log => log.Id)
                .Take(maxRecords).ToListAsync();
            var table2Task = context2.tableTwo
                .Where(log =>
                    log.DateUtc >= dateFrom || dateFrom == null)
                    && (log.DateUtc <= dateTo || dateTo == null))
                .OrderByDescending(log => log.Id)
                .Take(maxRecords).ToListAsync();
            var table3Task = context3.tableThree
                .Where(log =>
                    log.DateUtc >= dateFrom || dateFrom == null)
                    && (log.DateUtc <= dateTo || dateTo == null))
                .OrderByDescending(log => log.Id)
                .Take(maxRecords).ToListAsync();
           var resultTask = Task.WhenAll(table1Task, table2Task, table3Task);
           try
           
               resultTask.Wait();
               var result1 = table1Task.Result;
               var result2 = table2Task.Result;
               var result3 = table3Task.Result;
               // Use your 3 lists.
           
           catch(Exception ex)
           // Handle any exception when running.
           
        
    

每个查询都必须在单独的 DbContext 上运行,因为 DbContext 不是线程安全的。

正如 cmets 中所提到的,有更好的方法可以提高性能,包括索引和使用投影,而不是返回整个实体。如果 Table1 的行非常大,其中包含许多结果代码不需要的大字段,则使用 Select 进行投影以仅获取您需要的列可以帮助提供更高性能的代码。只有在运行多个查询时,我才会考虑并行性,其中一个或多个查询可能需要大量时间,并且每个查询的结果都是完全独立的。

【讨论】:

赞成。我会使用term“并发”而不是“并行化”。

以上是关于第一次调用后,对不同数据库上下文的 EF 查询似乎缓存的主要内容,如果未能解决你的问题,请参考以下文章

讨论过后而引发对EF 6.x和EF Core查询缓存的思考

EF中的预先加载和延迟加载

C# EF6 使用 Unity 对一个上下文进行多次异步调用 - Asp.Net Web Api

EF6 查询预编译

EF 调用存储过程

EF Core 在对现有查询添加查询时附加所有实体 [重复]