У меня есть представление, которое выполняется быстро (несколько секунд) до 41 записи (например, TOP 41
), но занимает несколько минут для 44 или более записей, с промежуточными результатами при запуске с помощью TOP 42
или TOP 43
. В частности, он вернет первые 39 записей за несколько секунд, а затем остановится почти на три минуты, а затем вернет оставшиеся записи. Этот шаблон одинаков при запросе TOP 44
или TOP 100
.
Это представление изначально получено из базового представления, добавляя к базе только один фильтр, последний в приведенном ниже коде. Кажется, нет никакой разницы, цепляю ли я дочернее представление от базы или пишу дочернее представление с кодом из встроенной базы. Базовый вид возвращает 100 записей всего за несколько секунд. Я хотел бы думать, что я могу заставить детский взгляд бегать так же быстро, как и основа, а не в 50 раз медленнее. Кто-нибудь видел такое поведение? Какие-нибудь догадки относительно причины или решения?
Это поведение было последовательным в течение последних нескольких часов, так как я протестировал соответствующие запросы, хотя количество строк, возвращаемых до начала замедления, немного увеличилось и уменьшилось. Это не ново; Я смотрю на это сейчас, потому что общее время выполнения было приемлемым (<2 минуты), но я видел эту паузу в связанных файлах журнала, по крайней мере, в течение нескольких месяцев.
блокировка
Я никогда не видел, чтобы запрос был заблокирован, и проблема существует, даже если в базе данных нет других действий (как проверено sp_WhoIsActive). Базовый вид включает в себя NOLOCK
все, для чего это стоит.
Запросы
Вот урезанная версия дочернего вида с базовым видом для простоты. Это все еще показывает скачок во время выполнения в приблизительно 40 записях.
SELECT TOP 100 PERCENT
Map.SalesforceAccountID AS Id,
CAST(C.CustomerID AS NVARCHAR(255)) AS Name,
CASE WHEN C.StreetAddress = 'Unknown' THEN '' ELSE C.StreetAddress END AS BillingStreet,
CASE WHEN C.City = 'Unknown' THEN '' ELSE SUBSTRING(C.City, 1, 40) END AS BillingCity,
SUBSTRING(C.Region, 1, 20) AS BillingState,
CASE WHEN C.PostalCode = 'Unknown' THEN '' ELSE SUBSTRING(C.PostalCode, 1, 20) END AS BillingPostalCode,
CASE WHEN C.Country = 'Unknown' THEN '' ELSE SUBSTRING(C.Country, 1, 40) END AS BillingCountry,
CASE WHEN C.PhoneNumber = 'Unknown' THEN '' ELSE C.PhoneNumber END AS Phone,
CASE WHEN C.FaxNumber = 'Unknown' THEN '' ELSE C.FaxNumber END AS Fax,
TransC.WebsiteAddress AS Website,
C.AccessKey AS AccessKey__c,
CASE WHEN dbo.ValidateEMail(C.EMailAddress) = 1 THEN C.EMailAddress END, -- Removing this UDF does not speed things
TransC.EmailSubscriber
-- A couple dozen additional TransC fields
FROM
WarehouseCustomers AS C WITH (NOLOCK)
INNER JOIN TransactionalCustomers AS TransC WITH (NOLOCK) ON C.CustomerID = TransC.CustomerID
LEFT JOIN Salesforce.AccountsMap AS Map WITH (NOLOCK) ON C.CustomerID = Map.CustomerID
WHERE
C.DateMadeObsolete IS NULL
AND C.EmailAddress NOT LIKE '%@volusion.%'
AND C.AccessKey IN ('C', 'R')
AND C.CustomerID NOT IN (243566) -- Exclude specific test records
AND EXISTS (SELECT * FROM Orders AS O WHERE C.CustomerID = O.CustomerID AND O.OrderDate >= '2010-06-28') -- Only count customers who've placed a recent order
AND Map.SalesforceAccountID IS NULL -- Only count customers not already uploaded to Salesforce
-- Removing the ORDER BY clause does not speed things up
ORDER BY
C.CustomerID DESC
Этот Id IS NULL
фильтр отбрасывает большинство записей, возвращаемых BaseView
; без TOP
предложения они возвращают 1100 записей и 267K соответственно.
Статистика
Когда работает TOP 40
:
SQL Server parse and compile time: CPU time = 234 ms, elapsed time = 247 ms.
SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms.
SQL Server Execution Times: CPU time = 0 ms, elapsed time = 0 ms.
(40 row(s) affected)
Table 'CustomersHistory'. Scan count 2, logical reads 39112, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 752, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AccountsMap'. Scan count 1, logical reads 458, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times: CPU time = 2199 ms, elapsed time = 7644 ms.
Когда работает TOP 45
:
(45 row(s) affected)
Table 'CustomersHistory'. Scan count 2, logical reads 98268, physical reads 1, read-ahead reads 3, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Orders'. Scan count 1, logical reads 1788, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AccountsMap'. Scan count 1, logical reads 2152, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times: CPU time = 41980 ms, elapsed time = 177231 ms.
Я удивлен, увидев, что число чтений скачет в ~ 3 раза для этой скромной разницы в реальном выходе.
Сравнивая планы выполнения, они одинаковы, кроме количества возвращенных строк. Как и в приведенной выше статистике, фактическое количество строк для ранних шагов в TOP 45
запросе значительно выше , а не только на 12,5%.
В общих чертах, он сканирует индекс покрытия из Orders и ищет соответствующие записи из WarehouseCustomers; соединение цикла с TransactionalCustomers (удаленный запрос, точный план неизвестен); и объединить это с таблицей сканирования AccountsMap. Удаленный запрос составляет 94% от оценочной стоимости.
Разные заметки
Ранее, когда я выполнял расширенное содержимое представления как отдельный запрос, он выполнялся довольно быстро: 13 секунд для 100 записей. Сейчас я тестирую урезанную версию запроса без подзапросов, и этот гораздо более простой запрос требует трех минут, чтобы запросить более 40 строк, даже если он выполняется как самостоятельный запрос.
Дочернее представление включает в себя значительное количество операций чтения (~ 1M на sp_WhoIsActive), но на этом компьютере (восемь ядер, 32 ГБ ОЗУ, выделенный блок SQL 95%) это обычно не проблема.
Я удалил и воссоздал оба представления несколько раз без изменений.
Данные не включают в себя поля TEXT или BLOB. Одно поле включает UDF; удаление не предотвращает паузу.
Времена похожи, будь то запросы на самом сервере или на моей рабочей станции на расстоянии 1400 миль, поэтому задержка, скорее всего, присуща самому запросу, а не отправке результатов клиенту.
Примечания Re: решение
Исправление оказалось простым: замена LEFT JOIN
карты на NOT EXISTS
предложение. Это вызывает только одно крошечное различие в плане запроса: присоединение к таблице TransactionCustomers (удаленный запрос) после присоединения к таблице Map, а не до. Это может означать, что он запрашивает только необходимые записи с удаленного сервера, что сократит передаваемый объем в ~ 100 раз.
Обычно я первый, чтобы поболеть за NOT EXISTS
; это часто быстрее чем LEFT JOIN...WHERE ID IS NULL
конструкция, и немного более компактно. В этом случае это неудобно, потому что проблемный запрос построен на существующем представлении, и хотя поле, необходимое для предотвращения объединения, предоставляется базовым представлением, оно сначала преобразуется из целого числа в текст. Поэтому для достойной производительности я должен отбросить двухслойный шаблон и вместо этого иметь два почти идентичных представления, причем второе включает NOT EXISTS
предложение.
Спасибо всем за помощь в устранении этой проблемы! Это может быть слишком специфично для моих обстоятельств, чтобы помочь кому-то еще, но, надеюсь, нет. Если ничто иное, это пример того, NOT EXISTS
чтобы быть более чем немного быстрее, чем LEFT JOIN...WHERE ID IS NULL
. Но реальный урок, вероятно, состоит в том, чтобы гарантировать, что удаленные запросы объединяются настолько эффективно, насколько это возможно; План запроса утверждает, что он составляет 2% от стоимости, но он не всегда оценивает точно.
источник
Ответы:
Несколько вещей, чтобы попробовать:
Проверьте свои индексы
Все
JOIN
ключевые поля проиндексированы? Если вы часто используете это представление, я бы зашел так далеко, что добавил бы отфильтрованный индекс для критериев в представлении. Например...CREATE INDEX ix_CustomerId ON WarehouseCustomers(CustomerId, EmailAddress) WHERE DateMadeObsolete IS NULL AND AccessKey IN ('C', 'R') AND CustomerID NOT IN (243566)
Обновить статистику
FULLSCAN
. Если имеется большое количество строк, возможно, что данные значительно изменились, не вызывая автоматический пересчет.Очистить запрос
Сделайте
Map
JOIN
aNOT EXISTS
- вам не нужны никакие данные из этой таблицы, так как вам нужны только несовпадающие записиУдалить
ORDER BY
. Я знаю, что комментарии говорят, что это не имеет значения, но мне очень трудно в это поверить. Это может не иметь значения для ваших меньших наборов результатов, так как страницы данных уже кэшированы.источник
LEFT JOIN...WHERE Id IS NULL
, я получаю эту паузу; какNOT EXISTS
пункт, время выполнения - секунды. Я удивлен, но не могу спорить с результатами!Улучшение 1 Удалите вложенный запрос для заказов и преобразуйте его в объединение
Улучшение 2 - Храните отфильтрованные записи TransactionalCustomers в локальной временной таблице
Последний запрос
Пункт 3 - Я предполагаю, что у вас есть индексы на CustomerID, EmailAddress, OrderDate
источник
EXISTS
обычно быстрее, чемJOIN
в этом случае, и устраняет потенциальные ошибки. Я не думаю, что это будет улучшение вообще.EXISTS
обязательно. Кроме того, с точки зрения, я не могу кэшировать повторно использованные данные о клиентах, хотя я играл с фиктивной TVF без параметров.