巨大的内存授予导致 RESOURCE SEMAPHORE 等待其他查询

use*_*827 4 performance sql-server execution-plan memory-grant query-performance

问题:

我有几个查询需要大量内存授权(~7GB)。这些查询经常运行,这导致其他查询等待内存。所以我看到 RESOURCE_SEMAPHORE 等待类型。服务器信息:

  1. Microsoft SQL Server 2016 (SP2) (KB4052908) - Windows Server 2016 Standard 10.0(内部版本 14393:)上的 13.0.5026.0 (X64)
  2. 128 GB 内存
  3. 96 GB 作为最大服务器内存
  4. 数据库大小为 1.6 TB
  5. 启用事务复制
  6. 查询存储已禁用

我知道我已经发布了有关此服务器性能的类似问题。我试图从不同的角度看待事物或尝试以不同的方式解决问题。正如这篇文章中提到的,我有某种内存压力。现在我正在尝试修复查询中的问题或摆脱这个巨大的内存授权。

这是帖子中提到的查询的计划。

执行计划

我了解到这SORT,HASH JOIN,EXCHANGE(parallel Distribute Stream and Parallel Re partition stream and Parallel Gather Stream)是消耗内存的迭代器,我在我的执行计划中看到了这些。

我怎样才能从这个查询中减少这个巨大的内存授权。?甚至我也很困惑内存压力是否是由于大量查询授予的巨大内存造成的。?

这是我使用 SQLServer 探查器捕获查询时的实际查询。

exec sp_executesql N'SELECT TOP (@p__linq__6) 
    [PJ2].[FormId] AS [FormId], 
    [PJ2].[TNUMId] AS [TNUMId], 
    [PJ2].[TNUMDateTime] AS [TNUMDateTime], 
    [PJ2].[TNUMEventNumber] AS [TNUMEventNumber], 
    [PJ2].[EventNumber] AS [EventNumber], 
    [PJ2].[SubUnit] AS [SubUnit], 
    [PJ2].[EventClass] AS [EventClass], 
    [PJ2].[NatureOfEvent] AS [NatureOfEvent], 
    [PJ2].[EventType] AS [EventType], 
    [PJ2].[FileNumber] AS [FileNumber], 
    [PJ2].[EventStatus] AS [EventStatus], 
    [PJ2].[TNUMDate] AS [TNUMDate], 
    [PJ2].[FileDate] AS [FileDate], 
    [PJ2].[ComplainantFirstName] AS [ComplainantFirstName], 
    [PJ2].[ComplainantLastName] AS [ComplainantLastName], 
    [PJ2].[InvestBy] AS [InvestBy], 
    [PJ2].[SecondaryManager] AS [SecondaryManager], 
    [PJ2].[RejectionReason] AS [RejectionReason], 
    [PJ2].[InactiveReason] AS [InactiveReason], 
    [PJ2].[InactiveDate] AS [InactiveDate], 
    [PJ2].[Ag_Id] AS [Ag_Id], 
    [PJ2].[Queue] AS [Queue], 
    [PJ2].[SL] AS [SL], 
    [PJ2].[PrimaryManagerId] AS [PrimaryManagerId], 
    [PJ2].[WarehouseDistrictId] AS [WarehouseDistrictId], 
    [PJ2].[WarehouseIsSQ] AS [WarehouseIsSQ], 
    [PJ2].[WarehousePhone] AS [WarehousePhone], 
    [PJ2].[CityTwp] AS [CityTwp], 
    [PJ2].[County] AS [County], 
    [PJ2].[Institution] AS [Institution], 
    [PJ2].[TNUMTime] AS [TNUMTime], 
    [PJ2].[Prefix] AS [Prefix], 
    [PJ2].[StreetNumber] AS [StreetNumber], 
    [PJ2].[Street] AS [Street], 
    [PJ2].[RoadType] AS [RoadType], 
    [PJ2].[Suffix] AS [Suffix], 
    [PJ2].[Apartment] AS [Apartment], 
    [PJ2].[AtOrNear] AS [AtOrNear], 
    [PJ2].[State] AS [State], 
    [PJ2].[Zip] AS [Zip], 
    [PJ2].[Beat] AS [Beat], 
    [PJ2].[Reviewed] AS [Reviewed], 
    [PJ2].[WarehouseCounty] AS [WarehouseCounty], 
    [PJ2].[IsTrue] AS [IsTrue], 
    [PJ2].[EnquiredByUserId] AS [EnquiredByUserId], 
    [PJ2].[SecondaryManagerUserId] AS [SecondaryManagerUserId], 
    [PJ2].[DiaryNumber] AS [DiaryNumber], 
    [PJ2].[CI_Value] AS [CI_Value], 
    [PJ2].[CurrentEventStatus_Description] AS [CurrentEventStatus_Description]
    FROM ( SELECT [PJ2].[FormId] AS [FormId], [PJ2].[TNUMId] AS [TNUMId], [PJ2].[TNUMDateTime] AS [TNUMDateTime], [PJ2].[TNUMEventNumber] AS 
[TNUMEventNumber], [PJ2].[EventNumber] AS [EventNumber], [PJ2].[IsTrue] AS [IsTrue], [PJ2].[SubUnit] AS [SubUnit], [PJ2].[EventClass] AS [EventClass], 
[PJ2].[NatureOfEvent] AS [NatureOfEvent], [PJ2].[EventType] AS [EventType], [PJ2].[FileNumber] AS [FileNumber], [PJ2].[EventStatus] AS [EventStatus], [PJ2].[TNUMDate] AS [TNUMDate], [PJ2].[FileDate] AS [FileDate], [PJ2].[ComplainantFirstName] AS [ComplainantFirstName], [PJ2].[ComplainantLastName] AS [ComplainantLastName], [PJ2].[InvestBy] AS [InvestBy], [PJ2].[SecondaryManager] AS [SecondaryManager], [PJ2].[EnquiredByUserId] AS [EnquiredByUserId], [PJ2].[SecondaryManagerUserId] AS [SecondaryManagerUserId], [PJ2].[RejectionReason] AS [RejectionReason], [PJ2].[InactiveReason] AS [InactiveReason], [PJ2].[InactiveDate] AS [InactiveDate], [PJ2].[Ag_Id] AS [Ag_Id], [PJ2].[Queue] AS [Queue], [PJ2].[SL] AS [SL], [PJ2].[PrimaryManagerId] AS [PrimaryManagerId], [PJ2].[WarehouseDistrictId] AS [WarehouseDistrictId], [PJ2].[WarehouseIsSQ] AS [WarehouseIsSQ], [PJ2].[WarehousePhone] AS [WarehousePhone], [PJ2].[CityTwp] AS [CityTwp], [PJ2].[County] AS [County], [PJ2].[Institution] AS [Institution], [PJ2].[TNUMTime] AS [TNUMTime], [PJ2].[Prefix] AS [Prefix], [PJ2].[StreetNumber] AS [StreetNumber], [PJ2].[Street] AS [Street], [PJ2].[RoadType] AS [RoadType], [PJ2].[Suffix] AS [Suffix], [PJ2].[Apartment] AS [Apartment], [PJ2].[AtOrNear] AS [AtOrNear], [PJ2].[State] AS [State], [PJ2].[Zip] AS [Zip], [PJ2].[Beat] AS [Beat], [PJ2].[Reviewed] AS [Reviewed], [PJ2].[CI_Value] AS [CI_Value], [PJ2].[CurrentEventStatus_Description] AS [CurrentEventStatus_Description], [PJ2].[DiaryNumber] AS [DiaryNumber], [PJ2].[WarehouseCounty] AS [WarehouseCounty], row_number() OVER (ORDER BY [PJ2].[Ag_Id] ASC, [PJ2].[TNUMEventNumber] ASC, [PJ2].[FileNumber] ASC) AS [row_number]
        FROM ( SELECT 
            [Ex1].[FormId] AS [FormId], 
            [Ex1].[TNUMId] AS [TNUMId], 
            [Ex1].[TNUMDateTime] AS [TNUMDateTime], 
            [Ex1].[TNUMEventNumber] AS [TNUMEventNumber], 
            [Ex1].[EventNumber] AS [EventNumber], 
            [Ex1].[IsTrue] AS [IsTrue], 
            [Ex1].[SubUnit] AS [SubUnit], 
            [Ex1].[EventClass] AS [EventClass], 
            [Ex1].[NatureOfEvent] AS [NatureOfEvent], 
            [Ex1].[EventType] AS [EventType], 
            [Ex1].[FileNumber] AS [FileNumber], 
            [Ex1].[EventStatus] AS [EventStatus], 
            [Ex1].[TNUMDate] AS [TNUMDate], 
            [Ex1].[FileDate] AS [FileDate], 
            [Ex1].[ComplainantFirstName] AS [ComplainantFirstName], 
            [Ex1].[ComplainantLastName] AS [ComplainantLastName], 
            [Ex1].[InvestBy] AS [InvestBy], 
            [Ex1].[SecondaryManager] AS [SecondaryManager], 
            [Ex1].[EnquiredByUserId] AS [EnquiredByUserId], 
            [Ex1].[SecondaryManagerUserId] AS [SecondaryManagerUserId], 
            [Ex1].[RejectionReason] AS [RejectionReason], 
            [Ex1].[InactiveReason] AS [InactiveReason], 
            [Ex1].[InactiveDate] AS [InactiveDate], 
            [Ex1].[Ag_Id] AS [Ag_Id], 
            [Ex1].[Queue] AS [Queue], 
            [Ex1].[SL] AS [SL], 
            [Ex1].[PrimaryManagerId] AS [PrimaryManagerId], 
            [Ex1].[WarehouseDistrictId] AS [WarehouseDistrictId], 
            [Ex1].[WarehouseIsSQ] AS [WarehouseIsSQ], 
            [Ex1].[WarehousePhone] AS [WarehousePhone], 
            [Ex1].[CityTwp] AS [CityTwp], 
            [Ex1].[County] AS [County], 
            [Ex1].[Institution] AS [Institution], 
            [Ex1].[TNUMTime] AS [TNUMTime], 
            [Ex1].[Prefix] AS [Prefix], 
            [Ex1].[StreetNumber] AS [StreetNumber], 
            [Ex1].[Street] AS [Street], 
            [Ex1].[RoadType] AS [RoadType], 
            [Ex1].[Suffix] AS [Suffix], 
            [Ex1].[Apartment] AS [Apartment], 
            [Ex1].[AtOrNear] AS [AtOrNear], 
            [Ex1].[State] AS [State], 
            [Ex1].[Zip] AS [Zip], 
            [Ex1].[Beat] AS [Beat], 
            [Ex1].[Reviewed] AS [Reviewed], 
            [Ex1].[CI_Value] AS [CI_Value], 
            [Ex1].[CurrentEventStatus_Description] AS [CurrentEventStatus_Description], 
            [Ex1].[DiaryNumber] AS [DiaryNumber], 
            [Ex1].[WarehouseCounty] AS [WarehouseCounty]
            FROM (SELECT 
    [SAQE].[FormId] AS [FormId], 
    [SAQE].[TNUMId] AS [TNUMId], 
    [SAQE].[TNUMDateTime] AS [TNUMDateTime], 
    [SAQE].[TNUMEventNumber] AS [TNUMEventNumber], 
    [SAQE].[EventNumber] AS [EventNumber], 
    [SAQE].[IsTrue] AS [IsTrue], 
    [SAQE].[SubUnit] AS [SubUnit], 
    [SAQE].[EventClass] AS [EventClass], 
    [SAQE].[NatureOfEvent] AS [NatureOfEvent], 
    [SAQE].[EventType] AS [EventType], 
    [SAQE].[FileNumber] AS [FileNumber], 
    [SAQE].[EventStatus] AS [EventStatus], 
    [SAQE].[TNUMDate] AS [TNUMDate], 
    [SAQE].[FileDate] AS [FileDate], 
    [SAQE].[ComplainantFirstName] AS [ComplainantFirstName], 
    [SAQE].[ComplainantLastName] AS [ComplainantLastName], 
    [SAQE].[InvestBy] AS [InvestBy], 
    [SAQE].[SecondaryManager] AS [SecondaryManager], 
    [SAQE].[EnquiredByUserId] AS [EnquiredByUserId], 
    [SAQE].[SecondaryManagerUserId] AS [SecondaryManagerUserId], 
    [SAQE].[RejectionReason] AS [RejectionReason], 
    [SAQE].[InactiveReason] AS [InactiveReason], 
    [SAQE].[InactiveDate] AS [InactiveDate], 
    [SAQE].[Ag_Id] AS [Ag_Id], 
    [SAQE].[Queue] AS [Queue], 
    [SAQE].[SL] AS [SL], 
    [SAQE].[PrimaryManagerId] AS [PrimaryManagerId], 
    [SAQE].[WarehouseDistrictId] AS [WarehouseDistrictId], 
    [SAQE].[WarehouseIsSQ] AS [WarehouseIsSQ], 
    [SAQE].[WarehousePhone] AS [WarehousePhone], 
    [SAQE].[CityTwp] AS [CityTwp], 
    [SAQE].[County] AS [County], 
    [SAQE].[Institution] AS [Institution], 
    [SAQE].[TNUMTime] AS [TNUMTime], 
    [SAQE].[Prefix] AS [Prefix], 
    [SAQE].[StreetNumber] AS [StreetNumber], 
    [SAQE].[Street] AS [Street], 
    [SAQE].[RoadType] AS [RoadType], 
    [SAQE].[Suffix] AS [Suffix], 
    [SAQE].[Apartment] AS [Apartment], 
    [SAQE].[AtOrNear] AS [AtOrNear], 
    [SAQE].[State] AS [State], 
    [SAQE].[Zip] AS [Zip], 
    [SAQE].[Beat] AS [Beat], 
    [SAQE].[Reviewed] AS [Reviewed], 
    [SAQE].[CI_Value] AS [CI_Value], 
    [SAQE].[CurrentEventStatus_Description] AS [CurrentEventStatus_Description], 
    [SAQE].[DiaryNumber] AS [DiaryNumber], 
    [SAQE].[WarehouseCounty] AS [WarehouseCounty]
    FROM [dbo].[SAQE] AS [SAQE]) AS [Ex1]
            WHERE ((N''Public'' = [Ex1].[SL]) OR (N''Private'' = [Ex1].[SL]) OR ([Ex1].[PrimaryManagerId] = @p__linq__0) OR ( EXISTS (SELECT 
                1 AS [C1]
                FROM (SELECT 
    [fp].[Id] AS [Id], 
    [fp].[Ag_Id] AS [Ag_Id], 
    [fp].[UserId] AS [UserId]
    FROM [dbo].[fp] AS [fp]) AS [Extent2]
                WHERE ([Ex1].[FormId] = [Extent2].[Id]) AND ([Extent2].[UserId] = @p__linq__1) AND ([Extent2].[Ag_Id] = @p__linq__2)
            ))) AND (([Ex1].[Ag_Id] = @p__linq__3) OR (([Ex1].[Ag_Id] IS NULL) AND (@p__linq__3 IS NULL))) AND (([Ex1].[Queue] = @p__linq__4) OR (([Ex1].[Queue] IS NULL) AND (@p__linq__4 IS NULL)))
        )  AS [PJ2]
    )  AS [PJ2]
    WHERE [PJ2].[row_number] > @p__linq__5
    ORDER BY [PJ2].[Ag_Id] ASC, [PJ2].[TNUMEventNumber] ASC, [PJ2].[FileNumber] ASC',N'@p__linq__0 int,@p__linq__1 int,@p__linq__2 varchar(8000),@p__linq__3 varchar(8000),@p__linq__4 varchar(8000),@p__linq__5 int,@p__linq__6 int',@p__linq__0=9495,@p__linq__1=9495,@p__linq__2='3568',@p__linq__3='3568',@p__linq__4='1',@p__linq__5=0,@p__linq__6=100
Run Code Online (Sandbox Code Playgroud)

Joe*_*ish 10

从您发布的执行计划中:

查询在执行期间必须等待 43 秒才能获得 MemoryGrant

一旦获得内存授权,查询只需要 51.3 秒就可以执行。这意味着该查询总共有 45% 的时间用于等待内存授予。如果面临RESOURCE_SEMAPHORE这种极端的等待,我会立即使用资源调控器MAX_GRANT_PERCENT 查询提示限制查询的内存授予,并在稍后整理详细信息。SQL Server 对内存授予相当贪婪,在某些情况下,您可以在不降低查询性能的情况下降低查询的内存授予。

如果您使用的是等效的企业版,则只能实施资源调控器解决方案。您可以REQUEST_MAX_MEMORY_GRANT_PERCENT使用工作负载组的选项来限制查询的最大内存授予。如果您想限制许多查询的内存授予,或者如果您无法更改导致问题的查询的查询文本,则资源调控器是一个理想的解决方案。一个不幸的限制是,您只能在 SQL Server 2019 之前为该选项指定整数。

MAX_GRANT_PERCENT如果可以编辑查询文本,则可以在任何版本的 SQL Server 上实施该选项。也可以通过计划指南强制执行此提示,但我从未尝试过。查询提示方法允许您为提示指定小数,因此它比资源调控器更灵活。

就您应该限制内存授予的程度而言,我每次都会将其减少一半,直到RESOURCE_SEMAPHORE等待得到控制。现在,您的授权约为服务器可用的总可授权内存的 10.5% (0.25*7664448/18189472)。如果您想将其减半,那么您可以使用资源调控器将内存授予限制为 5% 或使用MAX_GRANT_PERCENT hint. 百分比是不同的,因为MAX_GRANT_PERCENT提示因素影响查询可用的最大内存授权,而资源调控器设置决定了查询可用的最大内存授权。

确实,过多地限制内存授予会导致对 tempdb 的溢出,这可能对性能不利。根据 IO 性能,溢出所花费的时间可能比您当前等待内存授予所花费的时间要少得多。使用其可用内存的最大百分比的运算符是节点 41 处的散列连接:

哈希连接

基于此,我最好的保守猜测是,您可以将查询内存授权从 7664448 KB 一直降低到 4350000 KB,并且仍然可以避免 tempdb 溢出。精确的数字只能通过测试来确定。

如果您需要其他选项,您可以考虑更改索引或进行其他调整以获得具有更少散列连接或排序的查询计划。您的基数估计非常适合这些运算符,并且与计划的其余部分相比,这些运算符处理的行数相对较多,因此这不是我的第一种方法。