SQL Server 是否可以为查询授予比实例可用的内存更多的内存

SEa*_*986 10 sql-server database-internals sql-server-2019 memory-grant

前几天有人问我,如果 SQL Server 想要运行一个查询,而该查询被授予的内存多于实例可用的内存,会发生什么情况。我最初的想法是我可能会看到RESOURCE_SEMAPHORE等待并且查询永远不会开始。

我做了一些测试来试图找出答案。

我的实例以 4000MB RAM 启动:

EXEC sys.sp_configure N'max server memory (MB)', N'4000'
GO
RECONFIGURE WITH OVERRIDE
GO
Run Code Online (Sandbox Code Playgroud)

如果我然后运行我的(故意可怕的)查询:

USE StackOverflow
SELECT      CONVERT(NVARCHAR(4000), u.DisplayName) AS DisplayName,
            CONVERT(NVARCHAR(MAX), u.DisplayName) AS Disp2,
            CONVERT(NVARCHAR(MAX), u.DisplayName) AS Disp3
FROM        dbo.Users AS u
            JOIN dbo.Posts p
                ON LTRIM(u.DisplayName) = LTRIM(p.Tags)
WHERE       u.CreationDate >= '2008-12-25'
            AND u.CreationDate < '2010-12-26'
ORDER BY    u.CreationDate;
Run Code Online (Sandbox Code Playgroud)

执行计划显示授予的内存为 732,008KB。

然后,我将实例可用的内存设置为低于此数字,然后重新启动实例:

EXEC sys.sp_configure N'max server memory (MB)', N'500' /* a value lower than the previous memory grant */
GO
RECONFIGURE WITH OVERRIDE
GO
Run Code Online (Sandbox Code Playgroud)

我再次运行查询,发现它被授予的内存比以前更少(93,176KB),但计划实际上是不同的形状。

然后,我再次运行查询并使用查询提示强制原始计划查看授予了哪些内存:

USE StackOverflow
SELECT      CONVERT(NVARCHAR(4000), u.DisplayName) AS DisplayName,
            CONVERT(NVARCHAR(MAX), u.DisplayName) AS Disp2,
            CONVERT(NVARCHAR(MAX), u.DisplayName) AS Disp3
FROM        dbo.Users AS u
            JOIN dbo.Posts p
                ON LTRIM(u.DisplayName) = LTRIM(p.Tags)
WHERE       u.CreationDate >= '2008-12-25'
            AND u.CreationDate < '2010-12-26'
ORDER BY    u.CreationDate
OPTION (RECOMPILE, USE PLAN N'<xml here>'
Run Code Online (Sandbox Code Playgroud)

我发现查询现在使用原始计划,但获得与它编译的计划非常相似的内存授予(93,168KB) -强制实际计划

这似乎反驳了我的理论,即我会看到RESOURCE_SEMAPHORE等待,并表明存在某种机制可以阻止 SQL Server 授予比查询可用的内存更多的内存(这似乎完全合理!)顺便说一句,如果我在同时会话中运行查询两次500MB 服务器设置,两个会话都会RESOURCE_SEMAPHORE等待似乎无限期的情况。

我可以在计划中看到MaxQueryMemory,这似乎是阻止 SQL Server 授予比查询可用的内存更多的内存的原因。

是否可以为单个查询授予比实例可用的内存更多的内存?如果不是,是MaxQueryMemory什么导致 SQL Server 不分配比可用内存更多的内存?这个数字是如何计算的?

注意 - 我的 StackOverflow 数据库的兼容级别为 130

Eri*_*ing 17

默认情况下,SQL Server 将允许任何查询使用最多 25% 的最大服务器内存来授予内存,但实际上它通常接近 20%。

资源总督对此有话要说:

坚果

但在我的演示虚拟机上,最大服务器内存设置为 90GB,我可以获得的最高单查询内存授予是 17GB,而不是 22GB。

WITH
    c AS 
(
SELECT
    c.*,
    n = 
        ROW_NUMBER() OVER 
        (
            PARTITION BY 
                c.PostId 
            ORDER BY 
                c.Score DESC
        )
FROM dbo.Comments AS c
)
SELECT
    c.*
FROM c
WHERE n = 0;
Run Code Online (Sandbox Code Playgroud)

这是它的部分计划:

坚果

使用sp_PressureDetector,您可以获得有关 SQL Server 内存使用情况的大量信息。为了将其保留在内存授予中,当我运行上述查询时会发生以下情况:

坚果

直到我运行了四个查询副本,其中一个副本才在资源信号量的队列中等待:

坚果

等待信息:

坚果

一般的思考方式是,SQL Server 会将最大服务器内存的大约 75% 分配给请求授权的查询。

在这种情况下,90GB 内存中可授予 68GB 内存。由于第四个查询将超过大约 75% 的标记,因此它必须等待。

一般来说,总授权加上 50% 必须可用于立即授予内存。如果没有,它将等待,有时直到强制获得较低的拨款(查看forced_grant_count中的列sys.dm_exec_query_resource_semaphores

您可以从以下来源更详细地了解 SQL Server 通常如何确定信号量队列和内存授予的优先级:

请记住,我的答案仅参考传统的行模式和行存储工作负载。添加批处理模式和列存储索引将改变一些行为,特别是关于最大服务器内存以及需要内存授予的行为。

在行模式下,最常见的情况是当查询计划具有以下功能时,您会看到授予查询的内存:

  • 排序
  • 哈希连接或聚合
  • 优化的嵌套循环

即使查询计划中没有 DML 请求排序来执行压缩,对列存储索引的插入也会请求内存。