OFFSET и FETCH, приводящие к значительному снижению производительности запроса, в том числе при OFFSET = 0 - PullRequest
0 голосов
/ 04 декабря 2018

У меня довольно сложный SQL-запрос, который возвращает около 20 столбцов из большого числа объединений, используемых для заполнения таблицы результатов в пользовательском интерфейсе.Он также использует пару CTE для предварительной фильтрации результатов.Ниже приведено приблизительное значение запроса (я закомментировал строки, которые определяют производительность)

Поскольку объем данных в БД увеличился, производительность запроса значительно упала, всего около 2500строк в основной таблице «Контракт».

Экспериментально я обнаружил, что, просто удалив порядок, смещение выборки в конце, производительность выросла с 30 секунд до 1 секунды!

order by 1 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY

Это не имеет смысла для меня.Последняя строка должна быть довольно дешевой, бесплатной, даже когда OFFSET равен нулю, так почему она добавляет 29 сек в мое время запроса?

Чтобы сохранить ту же функцию для SQL, я адаптировал ее так, чтобыСначала я выбираю в #TEMP, затем выполняю описанную выше процедуру order-offset-fetch для временной таблицы, затем удаляю временную таблицу.Это завершается примерно через 2-3 секунды.

Моя «оптимизация» кажется довольно неправильной, конечно, есть более разумный способ достичь той же скорости?

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

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

WITH tableOfAllContractIdsThatMatchRequiredStatus(contractId) 
AS (
    SELECT DISTINCT c.id
    FROM contract c 
    INNER JOIN site s ON s.ContractId = c.id
    INNER JOIN SiteSupply ss ON ss.SiteId = s.id AND ss.status != 'Draft'
    WHERE 
        ISNULL(s.Deleted, '0') = 0 
        AND ss.status in ('saved')
)
,tableOfAllStatusesForAContract(contractId, status) 
AS (
    SELECT DISTINCT c.id, ss.status
    FROM contract c 
    INNER JOIN site s ON s.ContractId = c.id
    INNER JOIN SiteSupply ss ON ss.SiteId = s.id AND ss.status != 'Draft'
    WHERE ss.SupplyType IN ('Electricity') AND ISNULL(s.Deleted, '0') = 0 
)

SELECT 
     [Contract].[Id]
    ,[Contract].[IsMultiSite]
    ,statuses.StatusesAsCsv
    ... lots more columns
    ,[WaterSupply].[Status] AS ws

--INTO #temp

FROM 
(
    SELECT 
        tableOfAllStatusesForAContract.contractId, 
        string_agg(status, ', ') AS StatusesAsCsv  
    FROM 
        tableOfAllStatusesForAContract
    GROUP BY 
        tableOfAllStatusesForAContract.contractId
) statuses

JOIN contract ON Contract.id = statuses.contractId
JOIN tableOfAllContractIdsThatMatchRequiredStatus ON tableOfAllContractIdsThatMatchRequiredStatus.contractId = Contract.id
JOIN Site ON contract.Id = site.contractId and site.isprimarySite = 1 AND ISNULL(Site.Deleted,0) = 0
... several more joins
JOIN [User] ON [Contract].ownerUserId = [User].Id

WHERE isnull(Deleted, 0) = 0 
AND
 (
 [Contract].[Id] = '12659' 
 OR [Site].[Id] = '12659'
 ... often more search term type predicates here
  )

--select * from #temp
order by 1
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY
--drop table #temp

1 Ответ

0 голосов
/ 11 декабря 2018

У меня не было ответа, поэтому я попытаюсь объяснить это сам, с моим, по общему признанию, плохим пониманием того, как работает SQL, и некоторыми указателями из Jeroen в комментариях выше.Это, вероятно, не правильно, но из того, что я обнаружил, это может быть правильно, и я знаю, как исправить мою непосредственную проблему, чтобы она могла помочь другим.

Я объясню это по аналогии, так какэто то, что, как я полагаю, происходит:

Представьте, что вы повар в ресторане, и вам нужно готовить большое количество блюд (rows in results).Вы знаете, что будет много, так как вы говорите вам об этом (TOP 10 or FETCH 10).

Вы тратите время на подбор множества необходимых ингредиентов (table joins) и оборудования, которое вы ''Вам понадобится и, как только поступит первый заказ, вы будете уверены, что будете действительно эффективны.Нарезать больше, что вам нужно для первого заказа, положить его в маленькие миски, готовые к использованию на последующих заказах.Первый заказ занимает довольно много времени (30 secs), поскольку вы планируете заранее и хотите, чтобы последующие блюда выходили как можно быстрее.

Однако, пока вы сидите на кухне в ожиданииследующие заказы .. тогда не приходят.Вот и все, только один заказ.Ну, это была пустая трата времени!Если бы вы просто попытались достать одно блюдо, вы могли бы сделать это намного быстрее (1sec), но вы заранее планировали что-то, что никогда не было нужно.

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

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

"If you want a answer to something, post something that's wrong on the internet and someone will be sure to correct you"

...