У меня есть большое представление, которое я использую из приложения. Я думаю, что сузил свою проблему с производительностью, но я не уверен, как ее исправить. Упрощенная версия представления выглядит так:
SELECT ISNULL(SEId + '-' + PEId, '0-0') AS Id,
*,
DATEADD(minute, Duration, EventTime) AS EventEndTime
FROM (
SELECT se.SEId, pe.PEId,
COALESCE(pe.StaffName, se.StaffName) AS StaffName, -- << Problem!
COALESCE(pe.EventTime, se.EventTime) AS EventTime,
COALESCE(pe.EventType, se.EventType) AS EventType,
COALESCE(pe.Duration, se.Duration) AS Duration,
COALESCE(pe.Data, se.Data) AS Data,
COALESCE(pe.Field, se.Field) AS Field,
pe.ThisThing, se.OtherThing
FROM PE pe FULL OUTER JOIN SE se
ON pe.StaffName = se.StaffName
AND pe.Duration = se.Duration
AND pe.EventTime = se.EventTime
WHERE NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
) Z
Это, вероятно, не оправдывает всей причины структуры запроса, но, возможно, дает вам представление - это представление объединяет две очень плохо спроектированные таблицы, которые я не контролирую, и пытается синтезировать из них некоторую информацию.
Итак, так как это представление используется из приложения, при попытке оптимизации я обертываю его в другой SELECT, например так:
SELECT * FROM (
-- … above code …
) Q
WHERE StaffName = 'SMITH, JOHN Q'
потому что приложение ищет конкретных сотрудников в результате.
Проблема, кажется, в COALESCE(pe.StaffName, se.StaffName) AS StaffName
разделе, и что я выбираю из представления StaffName
. Если я изменю это на pe.StaffName AS StaffName
или se.StaffName AS StaffName
, проблемы с производительностью исчезнут (но см. Обновление 2 ниже) . Но это не сработает, потому что одна или другая сторона FULL OUTER JOIN
могут отсутствовать, поэтому одно или другое поле может иметь значение NULL.
Могу ли я выполнить рефакторинг, заменив его COALESCE(…)
чем-то еще, что будет переписано в подзапрос?
Другие заметки:
- Я уже добавил несколько индексов, чтобы исправить проблемы с производительностью в остальной части запроса - без
COALESCE
него это очень быстро. - К моему удивлению, просмотр плана выполнения не поднимает никаких флагов, даже если
WHERE
включены подзапрос и оператор переноса . Моя общая стоимость подзапроса в анализаторе равна0.0065736
. Хммм. Выполнение занимает четыре секунды. - Изменение приложения для запроса по-другому
(например, возвратможет работать, но в крайнем случае - я действительно надеюсь, что смогу оптимизировать представление, не прибегая к прикосновению к приложению.pe.StaffName AS PEStaffName, se.StaffName AS SEStaffName
и выполнениеWHERE PEStaffName = 'X' OR SEStaffName = 'X'
) - Хранимая процедура, вероятно, имела бы для этого больше смысла, но приложение построено с использованием Entity Framework, и я не мог понять, как заставить его хорошо работать с SP, который возвращает тип таблицы (еще одна тема).
Индексы
Индексы, которые я добавил до сих пор, выглядят примерно так:
CREATE NONCLUSTERED INDEX [IX_PE_EventTime]
ON [dbo].[PE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[ThisThing])
CREATE NONCLUSTERED INDEX [IX_SE_EventTime]
ON [dbo].[SE] ([EventTime])
INCLUDE ([StaffName],[Duration],[EventType],[Data],[Field],[OtherThing])
Обновить
Хм ... Я попытался смоделировать пораженные изменения выше, и это не помогло. Т.е. до того как ) Z
я добавил AND (pe.StaffName = 'SMITH, JOHN Q' OR se.StaffName = 'SMITH, JOHN Q')
, но производительность такая же. Теперь я действительно не знаю, с чего начать.
Обновление 2
Комментарий @ypercube о необходимости полного объединения заставил меня осознать, что мой синтезированный запрос исключил, вероятно, важный компонент. Хотя да, мне нужно полное объединение, тест, который я провел выше, отбрасывая COALESCE
и проверяя только одну сторону соединения на ненулевое значение, сделал бы другую сторону полного соединения неактуальной , и оптимизатор, вероятно, использовал это факт, чтобы ускорить запрос. Кроме того, я обновил пример, чтобы показать, что StaffName
это на самом деле один из ключей соединения - что, вероятно, имеет большое значение для вопроса. Я также сейчас склоняюсь к его предположению, что разделение этого на трехсторонний союз вместо полного объединения может быть ответом и упростит изобилие тех, COALESCE
что я делаю в любом случае. Пробую сейчас.
источник
KeyField
, оба индексаINCLUDE
вStaffName
поле и некоторых других областях. Я могу опубликовать определения индекса в вопросе. Я работаю над этим на тестовом сервере, поэтому могу добавить любые индексы, которые, по вашему мнению, могут быть полезны для проб!WHERE pe.ThisThing = 1 AND se.OtherThing = 0
условие, которое отменяетFULL OUTER
соединение и делает запрос эквивалентным внутреннему соединению. Вы уверены, что вам нужно ПОЛНОЕ присоединение?INNER JOIN
,LEFT JOIN
сWHERE IS NULL
чеком, RIGHT JOIN с IS NULL) , а затемUNION ALL
три части. Таким образом, вам не нужно будет его использовать,COALESCE()
и это может (просто может) помочь оптимизатору разобраться в переписывании.Ответы:
Это было довольно давно, но поскольку ОП говорит, что это сработало, я добавляю его как ответ (не стесняйтесь исправлять его, если вы обнаружите, что что-то не так).
Попробуйте сломать внутренний запрос на три части (
INNER JOIN
,LEFT JOIN
сWHERE IS NULL
проверкой,RIGHT JOIN
сIS NULL
проверкой) , а затем изUNION ALL
трех частей. Это имеет следующие преимущества:В оптимизаторе доступно меньше вариантов преобразования для
FULL
объединений, чем для (более распространенных)INNER
иLEFT
объединений.Производная
Z
таблица может быть удалена (вы можете сделать это в любом случае) из определения представления.NOT(pe.ThisThing = 1 AND se.OtherThing = 0)
Будет нужен только наINNER
вступление части.Незначительное улучшение, использование
COALESCE()
будет минимальным , если таковые вообще (я предположил , чтоse.SEId
иpe.PEId
не обнуляемые. Если больше столбцов не обнуляемые, вы будете в состоянии удалить большеCOALESCE()
вызовов.)Более важным, оптимизатор может оттолкнуть какое - либо условие ваши запросы, которые включают эти столбцы (теперь это
COALESCE()
не блокирует толчок.)Все вышеперечисленное даст оптимизатору больше возможностей для преобразования / перезаписи любого запроса, использующего представление, чтобы он мог найти план выполнения, в котором можно использовать индексы для базовых таблиц.
В целом, представление может быть записано как:
источник
Моя интуиция заключалась бы в том, что это не должно быть проблемой, поскольку к тому времени, когда
COALESCE(pe.StaffName, se.StaffName) AS StaffName
все что-либо будет сделано, все строки из двух источников уже должны быть извлечены и сопоставлены, так что вызов функции представляет собой простое сравнение в памяти в памяти и -выбирать. Очевидно, что это не так, поэтому, возможно, что-то в одном из источников (если они являются представлениями или встроенными производными таблицами) или в базовых таблицах (т.е. отсутствие индексов) заставляет планировщика запросов думать, что ему нужно сканировать эти столбцы по отдельности.Без более подробной информации о конкретном запросе, который вы выполняете, структурах поддержки и планах запроса, все, что мы предлагаем, является предположением.
Чтобы попытаться сделать сравнение в конце концов, вы можете просто выбрать оба значения в deribed table (
pe.StaffName AS pe.StaffName, se.StaffName AS seStaffName
), а затем выполнить выборку во внешнем запросе (COALESCE(peStaffName, seStaffName) AS StaffName
), или вы даже можете поместить данные из внутреннего запроса в затем временная таблица выполняет внешний запрос, выбирая из этого (но для этого потребуется хранимая процедура, и в зависимости от количества строк этот дамп в базу данных tempdb может быть дорогостоящим и, следовательно, проблематичным по своей сути).источник
Z
настоящее время возвращается с ~ 1,5 млн строк. Я хочу, чтобы он переписал этот предикат в запрос,Z
чтобы он использовал индексы ... но теперь я также растерялся, потому что когда я помещаю туда предикат вручную, он по-прежнему не использует индекс ... так что теперь Я не уверен.