OFFSET和FETCH会导致查询性能大幅下降-包括当OFFSET = 0

时间:2018-12-04 10:55:00

标签: sql-server performance fetch paging

我有一个相当复杂的SQL查询,涉及从大量联接中返回大约20列,这些联接用于在UI中填充结果网格。它还使用几个CTE来预过滤结果。我在下面包括了近似的查询(我已经注释掉了固定性能的行)

随着数据库中数据量的增加,查询性能大打折扣,主表“合同”中只有大约2500行。

通过实验,我发现只需删除订单,最后进行偏移获取,性能就从大约30秒缩短到了1秒!

order by 1 OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY

这对我来说毫无意义。最后一行应该相当便宜,即使OFFSET为零也可以免费使用,那为什么还要增加29秒的查询时间呢?

为了对SQL保持相同的功能,我对其进行了修改,以便首先选择#TEMP,然后在temp表上执行上述order-offset-fetch,然后删除temp表。这将在大约2-3秒内完成。

我的“优化”感觉很不对劲,肯定有更明智的方法来达到相同的速度吗?

我还没有针对较大的数据集进行过广泛的测试,从本质上讲,这是一种快速恢复性能的快速解决方案。我怀疑随着数据大小的增长它是否会有效。

除主键上的聚簇索引外,表上没有索引。 Query Execution计划似乎没有显示任何主要瓶颈,但我不是解释它的专家。

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 :(得分:0)

我没有答案,因此我将尝试自己解释一下,因为我对SQL的工作原理认识不足,并且在上面的注释中有Jeroen的一些提示。这可能不正确,但是从我发现的情况来看可能是正确的,而且我确实知道如何解决眼前的问题,从而可以帮助他人。

我将以类推的方式进行解释,因为我认为这可能正在发生:

想象一下,您是一家餐馆的厨师,而您必须准备大量的饭菜(rows in results。您知道屋前告诉您的内容(TOP 10 or FETCH 10将会有很多。

您花时间确定所需的多种原料(table joins)和所需的设备,并且在收到第一笔订单时,请确保您将真正提高效率。切碎第一个订单所需的更多物品,将其放入小碗中,以备后继使用。第一份订单要花很长时间(30 secs),因为您要提前计划,并希望后续的菜肴尽快出炉。

但是,当您坐在厨房里等待下一个订单时,..请不要到达。就是这样,只需一个订单。好吧,那是浪费时间!如果您只是想拿出一盘菜,可以做得更快(1sec),但是您正在为不需要的东西做计划。

第二天晚上,您放弃了先前的策略,一次只做每个盘子。但是这一次有100个客户。您无法一次快速地交付它们。如果您像前一天晚上那样提前计划,则交付所有订单的时间将大大缩短。 (我尚未检验这个假设,但我希望它可能会发生)。

对于我的查询,我不知道会有1个结果还是100个结果,尽管我可能可以根据用户输入的搜索条件预先进行一些分析,但我可能必须调整UI以适应给我更多信息,以便我可以更好地进行预测,这意味着我可以为SQL选择合适的策略以预先使用。照原样,我已经针对少量结果进行了优化,这些结果目前可以正常工作-但我需要做一些更广泛的测试,以了解随着数据集的增长性能如何受到影响。

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