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 请求排序来执行压缩,对列存储索引的插入也会请求内存。