Производительность SQL,.Net Optimization vs Best Practices

Мне нужно подтверждение/объяснение от вас профессионалов/гуру со следующим, потому что моя команда говорит мне, что "это не имеет значения", и это разочаровывает меня:)

Фон: У нас есть SQL Server 2008, который используется нашим основным веб-приложением MVC3/.Net4. У нас около 200 + одновременных пользователей в любой момент. Сервер сильно ударяется (блокировки, тайм-ауты, общая медлительность), и я пытаюсь применить вещи, которые я узнал на протяжении своей карьеры и в моем последнем классе сертификации MS. Это те вещи, которые мы все пробурили ( "закрыть SQL-соединения STAT" ), и я пытаюсь объяснить моей команде, что эти "мелочи", хотя и не одна, меняют разницу, складываются в конце.

Мне нужно знать, влияет ли следующее на производительность или если это просто "лучшая практика"

1. Использование ключевого слова "ИСПОЛЬЗОВАНИЕ". Большинство их кода выглядит так:

public string SomeMethod(string x, string y) {
    SomethingDataContext dc = new SomethingDataContext();
    var x = dc.StoredProcedure(x, y);
}

Пока я пытаюсь сказать им, что USING закрывает/освобождает ресурсы быстрее:

using (SomethingDataContext dc = new SomethingDataContext()) {
    var x = dc.StoredProcedure(x, y);
}

Их аргумент состоит в том, что GC делает достаточно хорошую очистку работы после выполнения кода, поэтому ИСПОЛЬЗОВАНИЕ не оказывает большого влияния. Правда или ложь и почему?

2. Пулы соединений

Я всегда слышал, что создание пулов соединений может значительно ускорить работу любого веб-сайта (по крайней мере,.Net w/MSSQL). Я рекомендовал добавить следующее к нашим связующим строкам в файле web.config:

... "Pooling = True; Min Pool Size = 3; Max Pool Size = 100; Connection Тайм-аут = 10;"...

Их аргумент состоит в том, что .Net/MSSQL уже настраивает пулы соединений за кулисами и не нужно вставлять в наш web.config. Правда или ложь? Почему каждый другой сайт говорит, что объединение должно быть добавлено для оптимальной производительности, если оно уже настроено?

3. Минимизируйте # вызовов в DB

Поставщик Role/Membership, который поставляется с проектом .Net MVC по умолчанию, хорош - он удобен и делает большую часть работы для вас. Но эти ребята серьезно используют UsersInRoles() и используют его свободно, как глобальную переменную (она обращается к БД всякий раз, когда вызывается этот метод). Я создал "пользовательский объект", который загружает все роли upfront на каждой странице (вместе с некоторыми другими пользовательскими элементами, такими как GUID и т.д.), А затем запрашивает этот объект, если у пользователя есть роль.

В других частях веб-сайта есть операторы FOR, которые занимают более 200 раз и выполняют 20-30 запросов на каждом проходе = более 4000 вызовов базы данных. Это как-то делает это за считанные секунды, но то, что я хочу сделать, - это объединение вызовов 20-30 БД в один, так что он делает ОДИН вызов 200 раз (каждый цикл). Но поскольку SQL-профайлер говорит, что запрос занял "0 секунд", они аргументируют это так быстро и мало, что серверы могут обрабатывать такое большое количество запросов БД.

Мое мышление: "Да, эти запросы работают быстро, но они убивают общую производительность SQL-сервера". Может ли это быть фактором? Я не беспокоюсь ни о чем, или это (существенный) фактор, способствующий общим проблемам производительности сервера?

4. Другие оптимизации кода

Первый, который приходит на ум, использует StringBuilder против простой строковой переменной. Я понимаю, почему я должен использовать StringBuilder (особенно в циклах), но они говорят, что это не имеет значения - даже если им нужно писать строки 10k +, их аргумент в том, что прирост производительности не имеет значения.

Итак, все в целом, все мы учимся и пробуждаемся в нас ( "минимизируем сферу!" ), просто "лучшая практика" без реального выигрыша в производительности или все они способствуют РЕАЛЬНОЙ/измеримой потере производительности

ИЗМЕНИТЬ *** Спасибо, ребята, за все ваши ответы! У меня есть новый (5-й) вопрос, основанный на ваших ответах: На самом деле они не используют "ИСПОЛЬЗОВАНИЕ", так что это значит? Если соединение пулов происходит автоматически, связывает ли он соединения из пула, пока не появится GC? Возможно ли, что каждое открытое соединение с сервером SQL добавляет немного больше нагрузки на сервер и замедляет его?

Основываясь на ваших предложениях, я планирую провести серьезный бенчмаркинг/протоколирование времени подключения, потому что я подозреваю, что a) сервер работает медленно, b) они не закрывают соединения и c) Профайлер говорит, что он работал за 0 секунд, медленность может исходить из соединения.

Я очень ценю вашу помощь. Чувствительность снова

Ответ 1

Вставьте код, внесите изменения и сравнительный тест + профилируйте его с текущей кодовой базой. Тогда у вас будет несколько доказательств, подтверждающих ваши претензии.

Что касается ваших вопросов, здесь идет речь:

  • Вы всегда должны вручную распоряжаться классами, которые реализуют IDisposable, GC на самом деле не вызовет dispose, если класс также реализует финализатор, тогда он вызывается финализатором, однако в большинстве реализаций они только очищают неуправляемые ресурсы.

  • Верно, что в .NET Framework уже выполняется объединение пулов, я не уверен, что значения по умолчанию, но значения строки соединения просто будут там, чтобы вы могли их изменять.

  • Время выполнения инструкции SQL является лишь частью истории, в профайле SQL все, что вы увидите, это то, как долго заработал механизм базы данных для выполнения запроса, что вам не хватает, это время, которое требуется веб-сервер для подключения и получения результатов от сервера базы данных, поэтому, в то время как запрос может быть быстрым, вы можете сэкономить на большом количестве операций ввода-вывода и локальной сети путем пакетных запросов.

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

Ответ 2

Oye. Конечно, вы не можете позволить GC закрыть ваши подключения к базе данных для вас. GC может не произойти в течение долгого времени... иногда через несколько часов. Это происходит не сразу, как только переменная выходит за рамки. Большинство людей используют IDisposable, используя() {} синтаксис, что здорово, но, по крайней мере, что-то, где-то нужно вызвать connection.Close()

Ответ 3

  • Объекты, которые реализуют IDisposable и удерживают на управляемых ресурсах, также реализуют финилер, который будет гарантировать, что dispose вызывается во время GC, проблема в том, когда он вызывается, gc может занять много времени, чтобы сделать это, и вы migth нуждается в этих ресурсах до этого. Использование делает вызов для удаления, как только вы закончите с ним.

  • Вы можете изменить параметры объединения в webconfig, но теперь он включен по умолчанию, поэтому, если вы оставите параметры по умолчанию, вы ничего не набираете

  • Вам нужно не только подумать о том, сколько времени требуется для выполнения запроса, но также и времени соединения между сервером приложений и базой данных, даже если он на том же компьютере добавляет служебные данные.

  • StringBuilder не повлияет на производительность в большинстве веб-приложений, было бы важно, если бы вы объединили 2 раза в одну строку, но я думаю, что это хорошая идея использовать ее, поскольку ее легче читать.

Ответ 4

Я думаю, что у вас есть два отдельных вопроса.

  • Выполнение вашего кода
  • Производительность базы данных SQL Server

SQL Server

Есть ли у вас мониторинг для SQL Server? Знаете ли вы конкретно, какие запросы запускаются, что вызывает взаимоблокировки?

Я бы прочитал эту статью о взаимоблокировках и рассмотрел возможность установки блестящего Who активен, чтобы узнать, что действительно происходит на вашем SQL Server. Вы можете также рассмотреть возможность установки sp_Blitz Брент Озар. Это должно дать вам отличное представление о том, что происходит в вашей базе данных, и дать вам инструменты для исправления этой проблемы.

Другие проблемы с кодом

Я не могу прокомментировать другие проблемы с кодом на голове. Поэтому я сначала посмотрел бы на SQL-сервер.

Помните

  • Монитор
  • Определение проблем
  • Профиль
  • Fix
  • Перейдите к 1

Ответ 5

Ну, я не гуру, но у меня есть предложение: если они скажут, что вы ошибаетесь, скажите им: "Докажите, скажите мне тест! Покажите мне, что 4000 звонков так же быстро, как 200 вызывает и оказывает такое же влияние на сервер!"

То же самое остальное. Если вы не в состоянии заставить их доказать свою правоту, докажите их не так, с ясными, хорошо документированными тестами, которые показывают, что вы говорите правильно.

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

Ответ 6

Рискуя просто повторить то, что здесь сказали другие, здесь мой 2c по этому вопросу

Во-первых, вы должны тщательно подобрать свои битвы... Я бы не стал воевать с вашими коллегами на всех 4 баллах, потому что, как только вы не сможете доказать, что один из них, он закончился, и с их точки зрения они правильно, и вы ошибаетесь. Также имейте в виду, что никто не любит, когда говорят, что их красивый код - уродливый ребенок, поэтому я полагаю, что вы будете дипломатичным - не говорите "это медленно", говорят: "Я нашел способ сделать это еще быстрее".... (конечно, ваша команда может быть вполне разумной, поэтому я основываю это на собственном опыте:) Итак, вам нужно выбрать одну из 4-х областей выше, чтобы заняться первым.

Мои деньги на №3. 1, 2 и 4 могут иметь значение, но в моем собственном опыте не так много, но то, что вы описали в № 3, звучит как смерть тысячами papercuts для бедного старого сервера! Запросы, вероятно, выполняются быстро, потому что они параметризованы, поэтому они кэшируются, но вам нужно помнить, что "0 секунд" в профилировщике может составлять 900 миллисекунд, если вы понимаете, что я имею в виду... добавьте это для многих и все начинает замедляться; это также может быть основным источником блокировок, потому что, если каждый из этих вложенных запросов снова и снова сталкивается с одной и той же таблицей, независимо от того, насколько быстро он работает, с количеством пользователей, о которых вы упомянули, это наверняка будет иметь соперничество. Возьмите SQL и запустите его в SSMS, но включите статистику клиентов, чтобы вы могли видеть не только время выполнения, но и количество данных, отправляемых клиенту; что даст вам более четкое представление о том, какие накладные расходы задействованы.

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

Чтобы коснуться вашего пятого вопроса - я не уверен, но я бы предположил, что любой SqlConnection, который не автоустанавливается (с использованием), считается еще "активным" и больше недоступен из пула. Сказанное - накладные расходы на соединение довольно низки на сервере, если соединение фактически ничего не делает, но вы можете снова доказать это, используя счетчики производительности SQL.

Удачи вам в этом, не могу дождаться, чтобы узнать, как вы поживаете.

Ответ 7

Предложение using - это просто синтаксический сахар, вы по существу делаете

try
{
    resouce.DoStuff();
}
finally
{
     resource.Dispose()
}

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

i), если мы привыкём к использованию, мы убеждаемся в освобождении неуправляемых ресурсов, потому что не все программисты-каркасы умны для реализации одноразового шаблона.

ii) да, GC в конечном итоге очистит этот объект, но это может занять некоторое время, в зависимости от того, насколько старый этот объект. Очистка Gen 2 GC выполняется только один раз в секунду.

Итак, коротко:

  • см. выше

  • да, объединение по умолчанию равно true и максимальному размеру пула до 100

  • вы правы, безусловно, лучшая область для продвижения по улучшению.

  • преждевременная оптимизация - это корень всего зла. Сначала получите №1 и №3. Использовать SQL профилировщик и db-специфические методы (добавлять индексы, дефрагментировать их, отслеживать взаимоблокировки и т.д.).

  • да, может быть. лучший способ - измерить его - посмотреть на первичную счетчик SQLServer: Общая статистика - User Connections; здесь - статья, описывающая, как это сделать.

Всегда измеряйте свои улучшения, не изменяйте код без доказательств!

Ответ 8

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

Мне удалось определить, что ошибка произошла только тогда, когда экземпляр SmtpClient был закрыт, что происходило при размещении SmtpClient, что происходило только во время сбора мусора.

И я заметил, что это часто занимало две минуты после нажатия кнопки "Отправить"...

Излишне говорить, что код теперь правильно реализует блоки using для экземпляров SmtpClient и MailMessage.

Просто слово мудрому...

Ответ 9

1 был рассмотрен значительно выше (я согласен с ним, что он неплохо распоряжается, и нашел, что это хорошая практика).

2 - это бит удержания от предыдущих версий ODBC, в котором соединения SQL Server были настроены независимо в отношении объединения. Раньше это было не по умолчанию; теперь он по умолчанию.

Что касается 3 и 4, то 4 не повлияет на производительность вашего SQL Server - StringBuilder может ускорить процесс в пользовательском интерфейсе, что может привести к ускорению закрытия ваших SQL-ресурсов быстрее, но они не уменьшит нагрузку на SQL Server.

3 звучит как самое логичное место для меня сосредоточиться. Я стараюсь как можно быстрее закрыть мои подключения к базе данных и сделать возможным наименьшее количество вызовов. Если вы используете LINQ, потяните все в IQueryable или что-то (список, массив, что угодно), чтобы вы могли манипулировать им и создавать любые структуры пользовательского интерфейса, которые вам нужны, и освобождать соединение до любого из этих хокумов.

Все сказанное, похоже, вам нужно потратить еще некоторое время с профилировщиком. Вместо того, чтобы смотреть на количество времени, которое выполнялось каждый раз, посмотрите на использование процессора и памяти. Просто потому, что они быстры, это не значит, что они не "голодные" казни.