Улучшить отчетность о времени выполнения хранимой процедуры — настроить временные таблицы?

Мне было поручено повысить производительность (и это мои первые реальные задачи по настройке производительности) хранимой процедуры создания отчетов, которая вызывается внешним интерфейсом SSRS, и в настоящее время хранимая процедура занимает около 30 секунд для работы с наибольшим объемом данных (на основе фильтров, установленных во внешнем интерфейсе отчета).

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

Я создал запрос на основе нескольких DMV, чтобы выяснить, какие из хранимых процедур являются наиболее ресурсоемкими запросами (небольшой фрагмент ниже), и я нашел один запрос, выполнение которого в среднем занимает около 10 секунд. .

select
    object_name(st.objectid)                                                                    [Procedure Name]
    , dense_rank() over (partition by st.objectid order by qs.last_elapsed_time desc)           [rank-execution time]
    , dense_rank() over (partition by st.objectid order by qs.last_logical_reads desc)          [rank-logical reads]
    , dense_rank() over (partition by st.objectid order by qs.last_worker_time desc)            [rank-worker (CPU) time]
    , dense_rank() over (partition by st.objectid order by qs.last_logical_writes desc)         [rank-logical write]
        ...
from sys.dm_exec_query_stats as qs
    cross apply sys.dm_exec_sql_text (qs.sql_handle) as st
    cross apply sys.dm_exec_text_query_plan (qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) as qp
where st.objectid in ( object_id('SuperDooperReportingProcedure') )
    , [rank-execution time]
    , [rank-logical reads]
    , [rank-worker (CPU) time]
    , [rank-logical write] desc

Теперь этот запрос немного странный в том смысле, что план выполнения показывает, что основная часть работы (~80%) выполняется при вставке данных в локальную временную таблицу, а не при опросе других таблиц, из которых исходные данные берутся, а затем обрабатываются. (скриншот ниже из SQL Sentry Plan Explorer)

введите здесь описание изображения

Кроме того, с точки зрения оценок строк, план выполнения имеет далеко идущие оценки для этого, в том смысле, что в локальную временную таблицу вставлено только 4218 строк, в отличие от ~248 тыс. строк, которые, по мнению плана выполнения, перемещаются в локальную временную таблицу. временная таблица. Итак, из-за этого я думаю о «статистике», но все же имеют ли они значение, если ~ 80% работы - это фактическая вставка в таблицу?

Одной из моих первых рекомендаций было переписать весь процесс и хранимую процедуру так, чтобы не включать перемещение и преобразование данных в хранимую процедуру создания отчетов, а выполнять преобразование данных каждую ночь в некоторые сохраняемые таблицы (данные в реальном времени). не требуется, только актуальные данные до конца предыдущего дня). Но бизнес-сторона не хочет вкладывать время и ресурсы в перепроектирование этого и вместо этого «предлагает» мне настроить производительность в смысле поиска, где и какие индексы я могу добавить, чтобы ускорить это.

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

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

В качестве нескольких четких вопросов, которые я был бы признателен, если бы можно было ответить:

  • Есть ли что-то неправильное в том, что я сказал выше (в моем понимании БД или моих предположениях)?
  • Верно ли, что добавление индекса во временную таблицу фактически увеличит время выполнения, поскольку таблица (и связанные с ней индексы перестраиваются/перестраиваются при каждом выполнении)?
  • Можно ли сделать что-нибудь еще в этом сценарии без необходимости переписывать процедуру/запросы и делать это только с помощью индексов или других методов настройки? (Я читал несколько заголовков статей о том, что вы также можете «настроить tempdb», но пока не вдавался в подробности).

Любая помощь очень ценится, и если вам нужна дополнительная информация, я буду рад опубликовать.

Обновление (2 августа 2016 г.):

Рассматриваемый запрос (частично) ниже. Чего не хватает, так это еще нескольких агрегированных столбцов и соответствующих им строк в разделе GROUP BY:

select
    b.ProgramName
    ,b.Region
    ,case when b.AM IS null and b.ProgramName IS not null 
        then 'Unassigned' 
        else b.AM 
    end as AM
    ,rtrim(ltrim(b.Store)) Store
    ,trd.Store_ID
    ,b.appliesToPeriod
    ,isnull(trd.countLeadActual,0) as Actual
    ,isnull(sum(case when b.budgetType = 0 and b.budgetMonth between @start_date and @end_date then b.budgetValue else 0 end),0) as Budget
    ,isnull(sum(case when b.budgetType = 0 and b.budgetMonth between @start_date and @end_date and (trd.considerMe = -1 or b.StoreID < 0) then b.budgetValue else 0 end),0) as CleanBudget
    ... 
into #SalvesVsBudgets
from #StoresBudgets b
    left join #temp_report_data trd on trd.store_ID = b.StoreID and trd.newSourceID = b.ProgramID
where (b.StoreDivision is not null or (b.StoreDivision is null and b.ProgramName = 'NewProgram'))
    group by
        b.ProgramName
        ,b.Region
        ,case when b.AM IS null and b.ProgramName IS not null 
            then 'Unassigned' 
            else b.AM 
        end
    ,rtrim(ltrim(b.Store))
    ,trd.Store_ID
    ,b.appliesToPeriod
    ,isnull(trd.countLeadActual,0)

Я не уверен, что это действительно полезно, но поскольку @kcung запросил это, я добавил информацию.

Кроме того, чтобы ответить на некоторые его вопросы:

  • временные таблицы не имеют индексов на них
  • Размер оперативной памяти: 32 ГБ

Обновление (3 августа 2016 г.):

Я попробовал предложения @kcung по перемещению операторов CASE из запроса, генерирующего совокупность, и, к сожалению, в целом время процедуры заметно не улучшилось, поскольку оно все еще колеблется в диапазоне от ± 0,25 до ± 1,0 секунды (да, оба ниже и больше времени, чем исходная версия хранимой процедуры, но я предполагаю, что это связано с переменной рабочей нагрузкой на моей машине).

План выполнения для того же запроса, но измененный для удаления условий CASE и оставления только агрегатов SUM, теперь выглядит следующим образом:

введите здесь описание изображения


person Radu Gheorghiu    schedule 02.08.2016    source источник


Ответы (2)


  1. Добавление индексов во временную таблицу определенно улучшит вызов чтения, но замедлит вызовы записи во временную таблицу.
  2. Здесь, как вы упомянули, в процедуре выполняется 19 запросов, поэтому анализ только одного запроса с планом выполнения не будет более полезным.
  3. Добавляя больше, если возможно, выполните только этот запрос и проверьте, сколько времени он занимает (затронутые строки).
  4. Другой подход, который вы можете попробовать, не уверен, что это возможно в вашем случае, попробуйте использовать табличную переменную вместо временной таблицы. Это связано с тем, что использование табличной переменной вместо временной таблицы имеет дополнительные преимущества, такие как предварительная компиляция процедуры и отсутствие ведения журналов транзакций. и многое другое, вам не нужно писать таблицу отбрасывания.
person Aditya    schedule 02.08.2016
comment
2. Большинство запросов SELECT из одной временной таблицы в другую (SELECT INTO FROM), с несколькими модификациями данных, основанными на еще нескольких объединениях/агрегациях и т. д. И есть еще несколько запросов, которые в сумме составляют еще около 15 секунд. (но это 4-5 запросов, а не 1). Итак, я пытаюсь сократить как можно больше времени на этот один большой запрос, прежде чем переходить к другим, которые, я не думаю, можно улучшить (пока). - person Radu Gheorghiu; 02.08.2016
comment
3. Я не думаю, что смогу получить более подробную информацию об этих запросах из какого-либо другого места, кроме DMV. Таким образом, добавление/тестирование большего количества запросов за раз не поможет мне найти что-то новое, поскольку я уже знаю, сколько времени это занимает и кто мои виновники. - person Radu Gheorghiu; 02.08.2016
comment
4. Я попробовал табличные переменные, и время выполнения улучшилось, но только на 5-10% (0,25 - 1 сек) примерно после 25 повторных исполнений, в среднем это около 0,5 сек. И я могу обвинить этот диапазон в загрузке сервера. Кроме того, насколько мне известно, табличные переменные все равно попадут в tempdb, если они не помещаются в памяти, поэтому задержка ввода-вывода снова увеличилась. Я согласен с журналом транзакций, но сейчас это не моя забота, и бизнес не хочет этого слышать (если он не может решить проблему). Кроме того, таблица удаления явно не указана, временные таблицы уничтожаются при закрытии сеанса. - person Radu Gheorghiu; 02.08.2016
comment
Поскольку он ссылается на временную таблицу при выборе других временных таблиц, я полагаю, что добавление индексов в такую ​​временную таблицу могло бы значительно ускорить процесс, если бы таблицы были достаточно большими. Мы делали это много раз для больших сложных процессов, и проще всего попробовать сначала, поскольку это не требует переписывания запросов, а затем тестирования, чтобы убедиться, что вы не изменили функциональность в процессе ускорения. Обычно мы вставляем во временную таблицу, создаем индекс, делаем другую работу и в конце удаляем индексы. - person HLGEM; 02.08.2016
comment
Табличные переменные обычно работают быстрее для небольших наборов данных, а индексированные временные таблицы обычно лучше подходят для больших. - person HLGEM; 02.08.2016

Есть ли шанс увидеть запрос? и индексы на обеих таблицах? Насколько велик ваш баран? насколько велика строка в каждой таблице (примерно)? Можете ли вы обновить статистику для обеих таблиц и повторно отправить планировщик запросов?

Чтобы ответить на ваш вопрос:

  1. Вы в основном правы, за исключением части добавления индексов. Добавление индексов поможет запросу выполнить поиск. Это также даст возможность планировщику запросов рассмотреть план соединения с вложенным циклом вместо плана хэш-соединения. К сожалению, я не могу ответить больше, пока не ответят на мой вопрос.
  2. Вам не нужно добавлять индекс во временную таблицу. Добавление индекса к этой временной таблице (или любой таблице назначения вставки) увеличит время записи, потому что вставка должна будет обновить этот индекс. Просто представьте индекс как копию вашей таблицы с меньшим количеством информации, и он находится поверх вашей таблицы, и он должен быть синхронизирован с вашей таблицей. Каждая запись (вставка, обновление, удаление) должна обновлять этот индекс.
  3. Глядя на итоговые строки обеих таблиц, этот запрос должен выполняться быстрее, чем 10 с, если у вас нет лимонного ПК, тогда это другая история.

EDIT: Просто хочу указать на пункт 2, я не знал, что исходная таблица также является временной таблицей. Временная таблица уничтожается после завершения каждого сеанса соединения. Добавление индекса во временную таблицу означает, что вы будете добавлять дополнительное время для создания этого индекса каждый раз, когда будете создавать эту временную таблицу.

EDIT: Извините, сейчас я использую телефон. Я просто буду краток. Итак, по сути 2 вещи:

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

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

Образец :

case when b.AM IS null and b.ProgramName IS not null 
    then 'Unassigned' 
    else b.AM 
end as AM

Вы можете создать столбец с именем AM при создании таблицы b. Также эти rtrim и ltrim. Пожалуйста, удалите их и вставьте во время создания таблицы. :)

person cungiderm    schedule 02.08.2016
comment
1. Обновлены вопросы с более подробной информацией. - person Radu Gheorghiu; 02.08.2016
comment
2. Нет индексов на временных таблицах. - person Radu Gheorghiu; 02.08.2016
comment
4. Думаете ли вы здесь о фрагментации страницы? - person Radu Gheorghiu; 02.08.2016
comment
5. Статистика для временных таблиц? Я не уверен, что это помогает, поскольку эти оценки взяты из данных из других временных таблиц и т. д. - person Radu Gheorghiu; 02.08.2016
comment
Итак, оперативная память не ваша проблема. Вы пытались добавить первичный ключ в обе таблицы во время создания? Вам не нужно беспокоиться о статистике, эти временные таблицы имеют ограниченное время жизни и создаются каждый раз, поэтому статистика будет актуальной. - person cungiderm; 02.08.2016
comment
И еще одна вещь, эти расчеты. Не могли бы вы сделать это во время создания таблицы? Таким образом, когда запрос выполняет группировку, он работает меньше. (все случаи когда) - person cungiderm; 02.08.2016
comment
Делать расчеты во время создания таблицы? Можете ли вы быть немного более явным, пожалуйста? (отредактируйте свой ответ, указав, как я должен изменить свой запрос) - person Radu Gheorghiu; 02.08.2016
comment
Итак, я проверил ваши предложения и переместил все операторы CASE в начало, где я создаю таблицу с псевдонимами "b", и, к сожалению, время (на всю процедуру) не уменьшилось. В моем втором запросе, где я просто помещаю данные в таблицу #SalesVBudgets, я оставил только агрегированные операции (SUM), и это не сильно изменилось (в целом). - person Radu Gheorghiu; 03.08.2016
comment
Итак, первичный ключ вам не очень помог. Не могли бы вы показать мне, что такое ФАКТИЧЕСКИЙ план запроса? Поэтому запустите свой запрос с включенной опцией «Включить фактический план выполнения» и вставьте его сюда. Нет необходимости в деталях. Я просто хочу посмотреть, что делает запрос. - person cungiderm; 03.08.2016
comment
Изображение, размещенное в моем вопросе, является фактическим планом выполнения, а не предполагаемым. - person Radu Gheorghiu; 03.08.2016
comment
Да, я понимаю, но поскольку вы изменили запрос, план запроса меняется. - person cungiderm; 03.08.2016
comment
О, так ты хочешь новый план. Выложу через пару минут. - person Radu Gheorghiu; 03.08.2016
comment
Временные таблицы — это в основном таблицы, созданные в базе данных tempdb. Поправьте меня, если я ошибаюсь, но вы используете вращающийся диск, не так ли? не ссд. У вас есть доступ к оборудованию? Я думаю, что самым быстрым (это было бы некрасиво) способом было бы вставить другой SSD на ваш ПК (я просто предполагаю, что вы на ПК), переместить tempdb на этот ssd, и вы должны увидеть множество улучшений записи для temp столы. - person cungiderm; 03.08.2016
comment
У меня сейчас нет такой информации. И, скорее всего, этого не произойдет, так как у меня все равно нет доступа ни к железу, ни к ресурсам, ни к чему-либо еще. Я здесь только для того, чтобы творить чудеса с помощью SQL :-) - person Radu Gheorghiu; 03.08.2016
comment
Так ты на облаке, да? РДС? Извините, мало чем могу помочь. Ваш запрос застрял на скорости записи. Вам нужно как-то ускорить запись, но вы уже на самом быстром маршруте (с точки зрения sql) :( - person cungiderm; 03.08.2016
comment
Из того, что я знаю до сих пор, это общая среда, но я не знаю о хранилище. Но было хорошо сузить круг и найти, в чем заключается мое ограничение, или что попробовать дальше. Спасибо за вашу помощь! (и если какие-либо другие идеи приходят в голову, пожалуйста, оставьте комментарий :-). Я сделаю это, если найду какое-либо другое решение. - person Radu Gheorghiu; 03.08.2016