在Azure或我的计算机上,我的ASP.NET/API或EF6 SQL数据库都非常慢

时间:2018-10-09 17:20:42

标签: c# asp.net sql-server entity-framework azure

我今天要来谈论服务器,我的服务器端很慢。事实是,我的服务器端越来越慢,日复一日,一周又一周,我不明白为什么...我写了这么多文章发布以使您处于我当前的背景/情况。这是一本很长的书,由于项目的机密性,我将尽我所能尽可能地从理论上入手。

感谢您花时间来帮助我:)


我的SQL Server计划具有以下信息:

  • 已用空间52 MB
  • 已分配空间64 MB
  • 最大大小250 GB
  • 定价层:Standard S0: 10 DTUs

我的AppPlan也具有以下详细信息:

  • 应用服务计划/定价层:Standard: 1 Small
    • 1个内核
    • 总共100个ACU
    • 1.75 GB内存
    • A系列计算

我的开发环境如下:

  • MacOS(MID 2015)(i7 2,8 GHz,16 GB 1600 MHz DDR3,High Sierra)
  • Win10(Parallel Desktop 13,已分配8GB)

项目的规格和配置:

  • ASP.NET(.NET Framework 4.6.1)
  • 实体框架6.2.0(代码优先)

在此数据库中,有静态数据和动态数据。事实是,即使我的数据库第一次通话真的很慢(没有预热),之后的通话仍然会很长。

我考虑了以下情况:

我们有12个人居住在9个不同的房屋中。这些人之间有关系,我们进行计算(不在数据库内部),计算出一定的分数,该分数与他们彼此之间的关系的重要性/权重(谈话量,他们之间通过联系建立的联系等)。

因此,在这种情况下,数据库仅需要通过其关系来检索用户的所有连接(最多11个)。一旦找到,我们便再次请求数据库来获取每个给定用户的房屋(包括其地址,房屋的详细信息)。然后,我们根据放置在最重要位置上的数据对用户进行排名来对C#进行计算。

但是,即使随着时间的推移,这种演算可能变得很沉重,它已经花费了0.8s至2.5s(有时需要15s或30s ...),而且确实很轻...

因此,我想在我的机器(VM)上进行尝试,并且几乎相同。但是,可以说,我们增加了用户可以更改其详细信息并编辑房屋的事实。好吧,性能下降太糟糕了……即使是很小的编辑也要花费0.8到1.5 ..从它开始,我开始认为对API的任何调用都是缓慢的。

但是,我也意识到并发也是一个大问题。如果我们的用户总共编辑了他们的一个信息(名字),并且同时调用了API以获取与其他用户的关系,那么一切都会变慢。.我不明白为什么,因为这仅仅是数据库中一个要编辑的单元格。另外,另一个是只读的(getRelations),所以我在我的LinQ中添加了AsNoTracking(),它没有任何改变。

为什么首先查看数据库是因为从Azure进入“探查器”部分时,我可以看到我的数据库有许多BLOCKED_TIME。另外,当并发发生时,我可以看到WAIT_TIME,为什么这会导致CPU阻塞或正在等待某事?由于我没有外部需求,因此我在互联网上搜索并找到了完整的答案:

https://stackoverflow.com/a/46553440/6093604


@dstj的答案What does AWAIT_TIME exactly mean in the Azure profiler?

  

来自Azure's documentation

     

等待(AWAIT_TIME)

     

AWAIT_TIME表示代码正在等待另一个任务完成。这通常在C#'await'语句中发生。当代码执行C#“等待”时,线程将展开并把控制权返回给线程池,并且没有任何线程被阻塞,等待“等待”完成。但是,逻辑上,等待的线程被“阻塞”,等待操作完成。 AWAIT_TIME表示等待任务完成的时间。+

     

封锁时间

     

BLOCKED_TIME表示代码正在等待另一个资源可用,例如等待同步对象,等待线程可用或等待请求完成。

     
     

因此,它正在等待继续进行处理所需的操作。我们在上传文件时遇到了AWAIT_TIME较长的问题,结果发现请求正在等待读取请求的流(对我们来说ReadAsMultiPartAsync())...如果您查看其中的代码RecASPRequest_RtlUserThreadStart,您可能是罪魁祸首...


“我正在查询数据库,但某些内容不可用” ...很抱歉,什么? 我可以提供一些屏幕截图:

enter image description here

因为找不到任何相关信息,所以我不断在网上搜索有关Entity Framework性能的信息,并找到了该博客:https://www.red-gate.com/simple-talk/dotnet/net-tools/entity-framework-performance-and-what-you-can-do-about-it/

这篇文章真的很有趣,我邀请您阅读:)从这篇文章中,我注意到一些重要的观点:

  • 多个结果集

      

    实体框架支持多个结果集,这使它可以通过单个连接向SQL Server发出和接收多个请求,从而减少了往返次数。如果您的应用程序服务器和数据库之间存在高延迟,则此功能特别有用。只需确保您的连接字符串包含:

    MultipleActiveResultSets=True;

    但是,如果确实改变了某些东西,我不完全了解它真正改变了什么?此外,即使在我的Azure端将其设置为false,在我的VM上也默认将其设置为true。这会成为我问题的根源吗?不能同时由多个实例访问数据库的事实?使用SQL(和/或EF6)不可能/无法实现吗?

  • 更改跟踪

      

    从数据库中检索实体时,可能会修改这些对象并希望能够将它们写回到数据库中。由于Entity Framework不了解您的意图,因此必须假设您将进行修改,因此必须设置这些对象以跟踪您所做的任何更改。这增加了额外的开销,也显着增加了内存需求。在检索更大的数据集时,这尤其成问题。

         

    如果您知道只想从数据库中读取数据(例如在MVC Controller中,只是获取数据以传递到View),则可以明确地告诉Entity Framework不要执行此跟踪:

    string city = "New York";
        List<School> schools = db.Schools
            .AsNoTracking()
            .Where(s => s.City == city)
            .Take(100)
            .ToList();
    

    如上所述,我已经尝试过了,但是就性能而言,目前还没有任何改变。当我获取更多数据时,可能会有所改变,但目前看来,跟踪并不是我性能下降问题的基础。

  • 缺少索引

    如果我完全理解这一部分,我认为它与我的问题没有任何联系,因为只有在我拥有大量数据的情况下,索引编制才会产生真正的影响,而只有12个用户的情况下,编制索引才有意义。

      

    我们可能想找到所有居住在纽约的学生。简单:

    string city = "New York";
    var pupils = db.Pupils
        .Where(p => p.City == city)
        .OrderBy(p => p.LastName)
        .Select(x => new { x.FirstName, x.LastName })
        .ToList();
    
  • 泛型查询

    即使我不认为以下内容与我的问题有关,我也无法理解为什么请求与实体的显示时间如此之久,因为我的行为与以下行为类似:

      

    通常,我们希望基于几个条件进行搜索。例如,我们可能有一组四个搜索框供用户完成,其中空框被忽略,因此编写如下内容:

    //Search data as input by user
    var searchModel = new Pupil
    {
        FirstName = "Ben",
        LastName = null,
        City = null,
        PostalZipCode = null
    };
    
    List<Pupil> pupils = db.Pupils.Where(p => searchModel.FirstName)
        && (searchModel.LastName == null || p.LastName == searchModel.LastName)
        && (searchModel.City == null || p.LastName == searchModel.City)
        && (searchModel.PostalZipCode == null || p.PostalZipCode == searchModel.PostalZipCode)
        )
    .Take(100)
    .ToList();
    
      

    很希望希望将所有的结果都为true的LastName,City和PostalZipCode子句在.NET中进行优化,因为在这种情况下它们为null,因此会在…行中留下查询。

    DECLARE @p__linq__0 NVARCHAR(20) = 'Ben'
    SELECT TOP 100
    PupilId ,
    FirstName ,
    LastName,
    etc...
    FROM dbo.Pupils
    WHERE FirstName = @p__linq__0
    
      

    我们会失望的-这不是EF查询生成的工作原理。如果我们检查实际执行的查询,则看起来像这样:

    -- Generated by ANTS Performance Profiler
    -- Executed against .\SQL2014
    USE [EFSchoolSystem]
    DECLARE @p__linq__0 NVarChar(4000) SET @p__linq__0 = 'Ben'
    DECLARE @p__linq__1 NVarChar(4000) SET @p__linq__1 = 'Ben'
    DECLARE @p__linq__2 NVarChar(4000) SET @p__linq__2 = ''
    DECLARE @p__linq__3 NVarChar(4000) SET @p__linq__3 = ''
    DECLARE @p__linq__4 NVarChar(4000) SET @p__linq__4 = ''
    DECLARE @p__linq__5 NVarChar(4000) SET @p__linq__5 = ''
    DECLARE @p__linq__6 NVarChar(4000) SET @p__linq__6 = ''
    DECLARE @p__linq__7 NVarChar(4000) SET @p__linq__7 = ''
    -- Executed query
    SELECT TOP (100)
    [Extent1].[PupilId] AS [PupilId] ,
    [Extent1].[FirstName] AS [FirstName] ,
    [Extent1].[LastName] AS [LastName] ,
    [Extent1].[Address1] AS [Address1] ,
    [Extent1].[Adderss2] AS [Adderss2] ,
    [Extent1].[PostalZipCode] AS [PostalZipCode] ,
    [Extent1].[City] AS [City] ,
    [Extent1].[PhoneNumber] AS [PhoneNumber] ,
    [Extent1].[SchoolId] AS [SchoolId] ,
    [Extent1].[Picture] AS [Picture]
    FROM [dbo].[Pupils] AS [Extent1]
    WHERE (@p__linq__0 IS NULL OR [Extent1].[FirstName] = @p__linq__1)
    AND (@p__linq__2 IS NULL OR [Extent1].[LastName] = @p__linq__3)
    AND (@p__linq__4 IS NULL OR [Extent1].[LastName] = @p__linq__5)
    AND (@p__linq__6 IS NULL OR [Extent1].[PostalZipCode] = @p__linq__7)
    
      

    对于任何LINQ语句,都会生成一个SQL查询,并且所有内容都由SQL Server处理。这个查询本身看起来很凌乱,但是由于您看不到它,为什么这么重要?毕竟,查询运行很快。 (请参阅另存为pdf的博客中的内容

    根据上面的解释,我尝试将请求拆分为多个Where而不是仅使用一个条件不同的结果,结果似乎是相同的。因此,在下一部分中,我感觉到数据类型不匹配。 / p>

  • 数据类型不匹配

      

    数据类型很重要,如果对它们没有足够的重视,那么即使是简单的数据库查询,也可能执行得很差。让我们看一个例子,说明原因。我们要搜索邮政编码为90210的学生。简单:

    string zipCode = "90210";
        var pupils = db.Pupils
        .Where(p => p.PostalZipCode == zipCode)
        .Select(x => new {x.FirstName, x.LastName})
        .ToList();
    
      

    不幸的是,从数据库返回结果需要很长时间。 “学生”表中有几百万行,但是有一个索引覆盖着我们要搜索的PostalZipCode列,因此应该尽快找到合适的行。的确,如果我们直接使用SQL Server Management Studio查询数据库,则会立即返回结果

    SELECT FirstName, LastName FROM Pupils p WHERE p.PostalZipCode = ‘90210’

      

    让我们看看应用程序在做什么。

         

    类型转换:CONVERT_IMPLICIT(nvarchar(20),[Extent1]。[PostalZipCode],0)= [@ p__linq__0]的寻求计划

         

    因此[Extent1]。[PostalZipCode]被隐式转换为NVARCHAR(20)。如果我们回头看一下已运行的完整查询,则可以看到原因。实体框架已将变量声明为NVARCHAR,由于.NET中的字符串为Unicode,而NVARCHAR是可表示Unicode字符串的SQL Server类型,因此似乎很明智。

         

    但是查看“学生”表,我们可以看到PostalZipCode列为VARCHAR(20)。为什么这是个问题?不幸的是,VARCHAR的Data Type Precedence比NVARCHAR低。这意味着无法将宽的NVARCHAR数据类型转换为较窄的VARCHAR,因为这可能会导致数据丢失(因为NVARCHAR可以表示VARCHAR不能表示的字符)。因此,为了将@ p__linq_0 NVARCHAR参数与表中的VARCHAR列进行比较,SQL Server必须将索引中的每一行都从VARCHAR转换为NVARCHAR。因此,它必须扫描整个索引。

         

    一旦您对此进行了追踪,就很容易修复。您只需要编辑模型以使用列批注明确告诉Entity Framework使用VARCHAR。

    public string Adderss2 { get; set; }
    [Column(TypeName = "varchar")]
    public string PostalZipCode { get; set; }
    

    在本段之后,我尝试了两件事,首先,当您想要检索他们的连接时,我的用户的年龄具有一定的作用。但是,如果应用程序方面发生问题,则可能是用户还没有年龄,因此我使用了int?,但是在我的WHERE子句中,我将其进行了比较设置为int的值。此外,我也不强制将string变量明确地设为varcharnvarchar,所以我开始这样做,我更改了所有内容, ,现在我在int?.Value子句中将intWHERE进行比较。

    但是...。同样长的时间...


基于此,我开始认为,即使我的SQL服务器即使执行一些简单的操作就达到100%的速度,并且如果发生任何并发操作,这些操作似乎都已暂停,但现在我的数据库可能不是问题。

因此,我检查了服务器端的问题是什么,我不知道该从哪里开始。.我已经花了将近一个星期的时间来尝试理解所有内容(因为我不是创建该问题的人。应用程序从头开始)。我看到了一些有关Context的东西(通过API请求获得一个),依赖项注入,但是即使不确定,我也不认为这是问题所在...

有人对我应该朝哪个方向有任何想法吗?有谁知道我可以在哪里找到导致这种缓慢行为的原因的一些数据?来自Azure Portal的VS 2017 Enterprise的任何一个

我希望我能提供足够多的细节,也许是太多了,我不知道,我只是想在当前背景/情况下尽可能多地介绍你

如果需要,我可以提供更多详细信息,请随时提出,非常感谢您的帮助!

最好

0 个答案:

没有答案