Для профилирования производительности первое место, которое я рекомендую посмотреть, это профилировщик SQL.Это может захватить точные операторы SQL, которые выполняет EF, и помочь определить возможные виновники производительности.Я расскажу о некоторых из них здесь .Вопросы Схемы, пожалуй, самое подходящее место для начала.Название предназначено для MVC, но большинство элементов относится к WPF и любому приложению.
Хороший, простой профилировщик, который я использую для SQL Server, - это ExpressProfiler.(https://github.com/OleksiiKovalov/expressprofiler)
С переходом на новый сервер и передачей данных по проводам, а не извлечением из локальной базы данных, проблемы производительности, которые вы заметили, скорее всего попадут в категорию«слишком много, слишком часто». Теперь вы будете не только ждать, пока база данных загрузит данные, но и упаковать их в пакет и отправить по проводам. Кроме того, новая база данных представляет собой то же самоеОбъем данных и обслуживать только одного клиента, или в настоящее время обслуживает несколько клиентов? Другие ловушки для разработчиков "работает на моем компьютере", когда локальные базы данных тестирования меньше и не имеют дело с параллельными запросами от сервера. (где блокировки и тому подобное могут повлиять на производительность)
Отсюда, запустите копию приложения с изолированным сервером базы данных (другие клиенты не используют его для уменьшения «шума») с запущенным на нем профилировщиком. На что обращать внимание:
Ленивая загрузка - это случаи, когда у вас есть запросы на загрузку данных, но затем вы видите много(от десятков до сотен) дополнительных запросов.Ваш код может сказать «запустить этот запрос и заполнить эти данные», который, как вы ожидаете, должен быть одним запросом SQL, но при касании лениво-загруженных свойств это может привести к появлению множества других запросов.Решение для отложенной загрузки: если вам нужны дополнительные данные, просто загрузите их с помощью .Include()
.Если вам нужны только некоторые данные, изучите использование .Select()
для выбора моделей просмотра / DTO нужных вам данных, а не полагайтесь на полные сущности.Это исключит сценарии с отложенной загрузкой, но может потребовать некоторых значительных изменений в вашем коде для работы с моделями представления / dtos.Такие инструменты, как Automapper, могут сильно помочь здесь.Прочтите .ProjectTo()
, чтобы узнать, как Automapper может работать с IQueryable
для устранения отложенных загрузок.
Чтение слишком много - загрузка сущностей может быть дорогой, особенно если вам не нужны все эти данные.Преступления в отношении производительности включают чрезмерное использование .ToList()
, которое материализует целые наборы сущностей, для которых требуется подмножество данных, или будет достаточно простой проверки или подсчета.Например, я видел код, который выполняет такие вещи:
var data = context.MyObjects.SingleOrDefault(x => x.IsActive && x.Id = someId);
return (data != null);
Это должно быть:
var isData = context.MyObjects.Where(x => x.IsActive && x.Id = someId).Any();
return isData;
Разница между ними заключается в том, что в первом примере EF будетэффективно выполнять операцию SELECT *, поэтому в случае, если данные присутствуют, они возвращают все столбцы в объект только для последующей проверки наличия объекта.Второй оператор выполнит более быстрый запрос, чтобы просто вернуть обратно, существует строка или нет.
var myDtos = context.MoyObjects.Where(x => x.IsActive && x.ParentId == parentId)
.ToList()
.Select( x => new ObjectDto
{
Id = x.Id,
Name = x.FirstName + " " + x.LastName,
Balance = calculateBalance(x.OrderItems.ToList()),
Children = x.Children.ToList()
.Select( c => new ChildDto
{
Id = c.Id,
Name = c.Name
}).ToList()
}).ToList();
Подобные выражения могут продолжаться и становиться довольно сложными, но настоящие проблемы - это .ToList () перед.Select ().Часто они закрадываются, потому что разработчики пытаются сделать что-то, чего не понимает EF, например, вызвать метод.(то есть CalculateBalance ()), и он «работает», сначала вызывая .ToList ().Проблема в том, что вы материализуете всю сущность в этот момент и переключаетесь на Linq2Object.Это означает, что любые «прикосновения» к связанным данным, таким как .Children, теперь будут вызывать ленивую загрузку, и снова дальнейшие вызовы .ToList()
могут насыщать больше данных в памяти, которые в противном случае могли бы быть уменьшены в запросе.Виновник, за которым нужно следить, это .ToList()
звонки и попытка их удаления.Выберите более простые значения перед вызовом .ToList (), а затем подайте эти данные в модели представлений, где модели представлений могут вычислять результирующие данные.
Худший виновник, подобный этому, я видел из-за того, что разработчик хотел использоватьфункция в предложении Where:
var data = context.MyObjects.ToList().Where(x => calculateBalance(x) > 0).ToList();
Этот первый оператор ToList()
попытается насытить всю таблицу до объектов в памяти.Большое влияние на производительность помимо времени / памяти / пропускной способности, необходимых для загрузки всех этих данных, заключается просто в количестве блокировок, которые база данных должна сделать для надежного чтения / записи данных.Чем меньше строк вы «трогаете» и чем короче вы их касаетесь, тем приятнее ваши запросы будут проигрываться при одновременных операциях нескольких клиентов.Эти проблемы значительно усиливаются по мере того, как системы переходят на использование большим количеством пользователей.
При условии, что вы устранили лишние ленивые нагрузки и ненужные запросы, следующее, на что нужно обратить внимание, - это производительность запросов.Для операций, которые кажутся медленными, скопируйте оператор SQL из профилировщика и запустите его в базе данных, просматривая план выполнения.Это может дать подсказки об индексах, которые вы можете добавить для ускорения запросов.Опять же, использование .Select()
может значительно повысить производительность запросов за счет более эффективного использования индексов и уменьшения объема данных, которые необходимо откатить серверу.
Для хранения файлов: хранятся ли они в виде столбцов в соответствующей таблице, илив отдельной таблице, которая связана с соответствующей записью?Под этим я подразумеваю, что если у вас есть запись счета-фактуры, а также копия файла счета-фактуры, сохраненная в базе данных, это:
счета-фактуры
- InvoiceId
- InvoiceNumber
- ...
- InvoiceFileData
или
Счета
- InvoiceId
- InvoiceNumber
- ...
InvoiceFile
- InvoiceId
- InvoiceFileData
Лучшая структура для хранения больших редко используемых данных в отдельных таблицах, а не в сочетании с обычно используемыми данными.Это сохраняет запросы на загрузку сущностей небольшими и быстрыми, где эти дорогостоящие данные могут быть получены по требованию при необходимости.
Если вы используете GUID для ключей (в отличие от целых / длинных), используете ли вы newsequentialid ()?(при условии, что SQL Server) Ключи настроены на использование newid () или в коде, Guid.New () приведет к фрагментации индекса и снижению производительности.Если вы заполняете идентификаторы через значения по умолчанию для базы данных, переключите их на использование newsequentialid (), чтобы уменьшить фрагментацию.Если вы заполняете идентификаторы с помощью кода, обратите внимание на написание генератора Guid, который имитирует newsequentialid () (SQL Server) или шаблон, подходящий для вашей базы данных.SQL Server и Oracle хранят / индексируют значения GUID по-разному, поэтому наличие статически-подобной части байтов UUID в старших и младших байтах данных будет способствовать повышению производительности индексирования.Также обратите внимание на обслуживание индекса и другие задания по обслуживанию базы данных, чтобы обеспечить эффективную работу сервера базы данных.
Когда дело доходит до настройки индекса, отчеты сервера базы данных - ваши друзья.После того, как вы удалили большинство или, по крайней мере, некоторых серьезных нарушителей производительности из своего кода, следующий шаг - это посмотреть на реальное использование вашей системы.Здесь лучше всего узнать, куда направить ваши исследования кода / индекса, - это наиболее часто используемые и проблемные запросы, которые определяет сервер базы данных.Там, где это EF-запросы, вы обычно можете провести обратный инжиниринг на основе тех таблиц, за которые отвечает EF-запрос.Захватите эти запросы и проведите их через план выполнения, чтобы увидеть, есть ли индекс, который может помочь.Индексирование - это то, что разработчики либо забывают, либо преждевременно беспокоятся.Слишком много индексов может быть так же плохо, как слишком мало.Я считаю, что лучше всего следить за использованием в реальных условиях, прежде чем принимать решение о том, какие индексы добавить.
Надеемся, что это должно дать вам представление о том, что нужно искать, и поднять скорость этой системы на ступеньку выше.:)