Мой ASP.NET/API или база данных SQL EF6 работают очень медленно, либо на Azure, либо на моей машине. - PullRequest
0 голосов
/ 09 октября 2018

Я приду сегодня, чтобы поговорить о сервере, моей медленной серверной стороне. Дело в том, что моя серверная сторона работает медленнее, день за днем, неделя за неделей, и я не понимаю, почему ... Я написал это огромноепост, чтобы поставить вас в моем текущем контексте / ситуации.Это долгое чтение, и я постараюсь максимально приблизить вас к нему из-за конфиденциальности проекта.

Спасибо за потраченное время, чтобы помочь мне :)


Мой план SQL Server содержит следующую информацию:

  • Используемое пространство 52 МБ
  • Выделенное пространство 64 МБ
  • Максимальный размер 250GB
  • Уровень цен: Standard S0: 10 DTUs

Мой AppPlan также содержит следующие данные:

  • План обслуживания приложения / уровень цен: Standard: 1 Small
    • 1x ядер
    • 100 всего ACU
    • 1,75 ГБ памяти
    • Вычисления серии A

Моя среда разработки выглядит следующим образом:

  • MacOS (MID 2015) (i7 2,8 ГГц, 16 ГБ, 1600 МГц DDR3, High Sierra)
  • Win10 (Parallel Desktop 13,Выделено 8 ГБ)

Характеристики и настройки проекта:

  • ASP.NET (.NET Framework 4.6.1)
  • Entity Framework 6.2.0(Code-First)

В этой базе данных есть статические данные и данные динамики.Дело в том, что моя база данных, даже если первый вызов действительно медленный (не разогретый), вызовы после будут продолжительными.

Я принял во внимание следующую ситуацию:

У нас 12 человек, живущих в 9 разных домах.У этих людей есть отношения, и мы делаем расчет (а не внутри базы данных), который вычисляет определенный балл, связанный с важностью / весом их отношений друг с другом (количество разговоров, связь, которую они имеют по своей связи и т. Д.).

Таким образом, в этом случае базе данных требуется только извлечь все соединения (макс. 11) пользователя по его отношению.Найдя, мы затем запрашиваем базу данных во второй раз, чтобы получить дом каждого данного пользователя (включая его адрес, детали дома).Затем мы делаем калькуляцию C # на основе данных, которые мы получили, чтобы отразить пользователей путем размещения наиболее важных.

Однако, даже если со временем это исчисление может быть тяжелым, оно уже занимает от 0,8с и 2,5 с (а иногда это занимает 15 с или 30 с ...), и это действительно легкий ...

Так что я хотел попробовать на моей машине (ВМ), и это почти то же самое.Однако, допустим, мы добавляем к нашей ситуации тот факт, что пользователь может изменить свои данные и отредактировать свой дом.Ну, производительность снижается так плохо ... Даже небольшое редактирование займет 0,8-1,5. Отсюда я начал думать, что любой вызов API медленный.

Однако я также понял, чтопараллелизм также является большой проблемой.Если наши пользователи, в общем, редактируют одну из своих данных (имя) и в то же время, один вызывает API, чтобы установить свои отношения с другими пользователями, тогда все так сильно замедляется .. Я не понимаю, почему, потому что это толькоодна ячейка в базе данных, которая редактируется.Кроме того, другой - только для чтения (getRelations), поэтому я добавил AsNoTracking() в свой LinQ, который ничего не изменил ..

Почему я сначала посмотрел на базу данных, потому что из Azureкогда я попадаю в раздел 'profiler', я вижу, что в моей базе данных много BLOCKED_TIME.Кроме того, когда происходит параллелизм, я вижу WAIT_TIME, почему, черт возьми, процессор заблокирован или что-то ждет?Так как у меня нет внешних потребностей, я искал в интернете и нашел полный ответ:

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


Ответ @ dstj для Что именно означает AWAIT_TIME в профилировщике Azure?

Из Документация Azure :

Ожидание (AWAIT_TIME)

AWAIT_TIME указывает, что код ожидает завершения другого задания.Обычно это происходит с оператором C # «ожидание».Когда код выполняет C # «ожидание», поток раскручивается и возвращает управление пулу потоков, и нет ни одного потока, который заблокирован, ожидая завершения «ожидания».Однако, логически поток, который ожидал, «заблокирован», ожидая завершения операции.AWAIT_TIME указывает на заблокированное время ожидания завершения задачи. +

Blocked 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 /

Этот пост действительно интересен, но я предлагаю вам прочитать его :) Из этого поста я заметил несколько важных моментов:

  • Несколько наборов результатов

    Entity Framework поддерживает множественные результирующие наборы, что позволяет ему отправлять и получать несколько запросов к SQL Server по одному соединению, уменьшая количество циклических переходов.Это особенно полезно, если между вашим сервером приложений и базой данных существует большая задержка.Просто убедитесь, что ваша строка подключения содержит:

    MultipleActiveResultSets=True;

    Однако, если это действительно что-то меняет, я не до конца понимаю, что это действительно меняет?Кроме того, даже если он установлен как false на моей стороне Azure, он по умолчанию установлен как true на моей виртуальной машине.Будет ли это источником моей проблемы?Тот факт, что база данных не может быть доступна нескольким экземплярам одновременно?Это невозможно / невозможно с помощью SQL (и / или EF6)?

  • Отслеживание изменений

    Когда вы извлекаете объекты из базы данных, возможно, что выбудет изменять эти объекты и ожидать, что сможет записать их обратно в базу данных.Поскольку Entity Framework не знает ваших намерений, она должна предполагать, что вы будете вносить изменения, поэтому необходимо настроить эти объекты для отслеживания любых внесенных изменений.Это добавляет дополнительные издержки, а также значительно увеличивает требования к памяти.Это особенно проблематично при получении больших наборов данных.

    Если вы знаете, что хотите только читать данные из базы данных (например, в контроллере MVC, который просто выбирает данные для передачи в представление), вы можете явно указать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();
    

    Соблазнительно надеяться, чтоПредложения LastName, City и PostalZipCode, все из которых имеют значение true, поскольку в этом случае они имеют нулевое значение, будут оптимизированы в .NET, оставляя запрос в виде…

    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)

    Исходя из приведенного выше объяснения, я попытался разделить свой запрос на несколько Где вместо одного с другим условием, и результат выглядит одинаковым.Поэтому в следующей части я почувствовал, что несовпадающие типы данных.

  • Несоответствующие типы данных

    Типы данных имеют значение, и если уделяется недостаточно вниманиядля них даже обезоруживающе простые запросы к базе данных могут работать на удивление плохо.Давайте посмотрим на пример, который продемонстрирует почему.Мы хотим найти учеников с почтовым индексом 90210. Легко:

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

    К сожалению, получение результатов из базы данных занимает очень много времени.В таблице Pupils есть несколько миллионов строк, но есть индекс, охватывающий столбец 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).Если мы оглянемся на завершенный запрос, мы увидим, почему.Entity Framework объявил переменную как NVARCHAR, что представляется разумным, поскольку строки в .NET являются Unicode, а NVARCHAR - это тип SQL Server, который может представлять строки Unicode.

    Но, глядя на таблицу Pupils, мы видим, чтоСтолбец PostalZipCode - это VARCHAR (20).Почему это проблема?К сожалению, VARCHAR имеет более низкий приоритет типа данных , чем 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 value .. Кроме того, я не заставляю мои string vars явно varchar или nvarchar, поэтому я начал это делать, я все изменил и теперь я сравниваю int?.Value с моим intвнутри моего WHERE пункта.

    Но .... То же самое количество тто есть ...


Исходя из этого, я начал думать, что, возможно, моя база данных не является проблемой в данный момент, даже если мой сервер SQL достигает 100% длядаже некоторые простые действия и действия кажутся приостановленными, если произойдет какой-либо параллелизм ..

Итак, я проверил, в чем проблема на стороне сервера, и я не знаю, с чего начать, если честно ..уже потратил почти неделю, пытаясь понять все (потому что я не тот, кто создает приложение с нуля).Я видел кое-что о контексте (чтобы получить запрос по API), внедрение зависимостей, но я не чувствую, что это будет проблемой, даже если я не уверен ...

Сделайте кого-нибудьимел бы какое-либо представление, в каком направлении я должен смотреть?Кто-нибудь знает, где я смогу найти некоторые данные о том, что вызывает такое медленное поведение?Либо из VS 2017 Enterprise Azure Portal

Надеюсь, я привел достаточно подробностей, может быть, это слишком много, я не знаю, я просто пытаюсь представить вас как можно больше в текущем контексте /ситуация

Я могу указать больше деталей, если вам нужно, поэтому не стесняйтесь просить об этом, большое спасибо за любую помощь!

Best,

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...