解决 SQL Server 中内存授予导致的性能低下或内存不足问题
什么是内存授予?
内存授予(也称为查询执行(QE)预留、查询执行内存、工作区内存和内存预留,描述查询执行时内存的使用情况。 SQL Server 在查询执行期间为以下一个或多个目的分配此内存:
- 排序运算符
- 哈希操作
- 大容量复制操作(不是常见问题)
- 索引创建,包括插入 COLUMNSTORE 索引,因为哈希字典/表在运行时用于索引生成(不是常见问题)
为了提供一些上下文,在生存期内,查询可能会根据需要执行的操作从不同的内存分配器或职员请求内存。 例如,最初分析并编译查询时,它将使用编译内存。 编译查询后,释放该内存,生成的查询计划将存储在计划缓存内存中。 缓存计划后,查询即可执行。 如果查询执行任何排序操作、哈希匹配操作(JOIN 或聚合),或插入到 COLUMNSTORE 索引中,它将使用查询执行分配器的内存。 最初,查询会请求该执行内存,如果授予此内存,查询将使用内存的全部或部分内存来对结果或哈希存储桶进行排序。 在查询执行期间分配的此内存称为内存授予。 可以想象,查询执行操作完成后,内存授予将释放回 SQL Server 以用于其他工作。 因此,内存授予分配在本质上是暂时性的,但仍可以持续很长时间。 例如,如果查询执行对内存中非常大的行集执行排序操作,则排序可能需要几秒钟或几分钟时间,并且授予的内存用于查询的生存期。
具有内存授予的查询示例
下面是使用执行内存及其查询计划的查询示例,其中显示了授予:
SELECT *
FROM sys.messages
ORDER BY message_id
此查询选择超过 300,000 行的行集并进行排序。 排序操作会引发内存授予请求。 如果在 SSMS 中运行此查询,可以查看其查询计划。 选择查询计划最 SELECT
左侧的运算符时,可以查看查询的内存授予信息(按 F4 显示 属性):
此外,如果在查询计划的空白中右键单击,可以选择“ 显示执行计划 XML...” ,并找到显示相同内存授予信息的 XML 元素。
<MemoryGrantInfo SerialRequiredMemory="512" SerialDesiredMemory="41232" RequiredMemory="5248" DesiredMemory="46016" RequestedMemory="46016" GrantWaitTime="0" GrantedMemory="46016" MaxUsedMemory="45816" MaxQueryMemory="277688" LastRequestedMemory="0" IsMemoryGrantFeedbackAdjusted="No: First Execution" />
此处需要解释几个术语。 查询可能需要一定数量的执行内存(DesiredMemory),并且通常会请求该数量(RequestedMemory)。 在运行时,SQL Server 会根据可用性(GrantedMemory)授予所有或部分请求的内存。 最后,查询可能会使用最初请求的内存(MaxUsedMemory)的更多或更少。 如果查询优化器过度估计了所需的内存量,则使用小于请求的大小。 但是,这种内存被浪费了,因为它可能被另一个请求使用。 另一方面,如果优化器低估了所需的内存大小,则多余的行可能会溢出到磁盘,以在执行时完成工作。 SQL Server 不分配比最初请求的大小更多的内存,而是将额外的行推送到磁盘,并将其用作临时工作区。 有关详细信息,请参阅内存授予注意事项中的 Workfiles 和 Worktable。
术语
让我们回顾一下有关此内存使用者可能遇到的不同术语。 同样,所有这些概念都描述了与相同内存分配相关的概念。
查询执行内存(QE 内存): 此术语用于突出显示在执行查询期间使用排序或哈希内存的事实。 通常,QE 内存是查询期间内存的最大使用者。
查询执行(QE)预留或内存预留: 当查询需要内存进行排序或哈希操作时,它会为内存发出预留请求。 该预留请求是根据估计基数在编译时计算的。 稍后,执行查询时,SQL Server 会根据内存可用性授予部分或完全请求。 最后,查询可能会使用授予内存的百分比。 有一个名为“MEMORYCLERK_SQLQERESERVATIONS”的内存职员(会计)跟踪这些内存分配(请查看 DBCC MEMORYSTATUS 或 sys.dm_os_memory_clerks)。
内存授予: 当 SQL Server 将请求的内存授予执行查询时,据说发生了内存授予。 有几个性能计数器使用术语“grant”。这些计数器,
Memory Grants Outstanding
并Memory Grants Pending
显示满足或等待的内存授予计数。 它们不考虑内存授予大小。 单独使用一个查询可以消耗 4 GB 内存来执行排序,但这不会反映在这两个计数器中的任何一个中。工作区内存 是描述相同内存的另一个术语。 通常,你可能会在 Perfmon 计数器
Granted Workspace Memory (KB)
中看到此术语,它反映当前用于排序、哈希、大容量复制和索引创建操作的总内存量(以 KB 表示)。 另Maximum Workspace Memory (KB)
一个计数器占任何可能需要执行此类哈希、排序、大容量复制和索引创建操作的请求的最大工作区内存量。 这两个计数器之外经常遇到术语工作区内存。
大型 QE 内存利用率的性能影响
在大多数情况下,当线程请求 SQL Server 中的内存完成操作且内存不可用时,请求失败并出现内存不足错误。 但是,存在一些异常情况,其中线程不会失败,而是等待内存可用。 其中一种方案是内存授予,另一种方案是查询编译内存。 SQL Server 使用名为信号灯的线程同步对象来跟踪为查询执行授予多少内存。 如果 SQL Server 耗尽了预定义的 QE 工作区,而不是由于内存不足错误导致查询失败,则会导致查询等待。 鉴于允许工作区内存占用大量总体 SQL Server 内存,因此在此空间中等待内存会产生严重的性能影响。 大量的并发查询请求执行内存,并且一起,它们耗尽了 QE 内存池,或者一些并发查询都请求了非常大的授予。 无论哪种方式,生成的性能问题可能具有以下症状:
- 缓冲区缓存中的数据和索引页可能已刷新,以便为大型内存授予请求提供空间。 这意味着必须满足来自查询请求的页面读取(操作速度明显较慢)。
- 对其他内存分配的请求可能会失败并出现内存不足错误,因为资源与排序、哈希或索引生成操作绑定在一起。
- 需要执行内存的请求正在等待资源可用,并且需要很长时间才能完成。 换句话说,对最终用户来说,这些查询速度较慢。
因此,如果观察 Perfmon 中的查询执行内存等待、动态管理视图(DMV),或者 DBCC MEMORYSTATUS
必须采取措施解决此问题,特别是如果问题频繁发生。 有关详细信息,请参阅 开发人员对排序和哈希操作执行的操作。
如何确定查询执行内存的等待
有多种方法可以确定 QE 预留的等待。 选择最适合你在服务器级别查看大图的图片。 其中一些工具可能不可用(例如,Perfmon 在Azure SQL 数据库中不可用)。 确定问题后,必须在单个查询级别向下钻取,以查看哪些查询需要优化或重写。
在服务器级别,使用以下方法:
- 资源信号灯 DMV sys.dm_exec_query_resource_semaphores 有关详细信息,请参阅 sys.dm_exec_query_resource_semaphores。
- 性能监视器计数器有关详细信息,请参阅 SQL Server 内存管理器对象。
- DBCC MEMORYSTATUS 有关详细信息,请参阅 DBCC MEMORYSTATUS。
- 内存 clerk DMV sys.dm_os_memory_clerks 有关详细信息,请参阅 sys.dm_os_memory_clerks。
- 使用扩展事件(XEvents)标识内存授予。有关详细信息,请参阅扩展事件(XEvents)。
在单个查询级别,使用以下方法:
- 识别具有sys.dm_exec_query_memory_grants的特定查询:当前正在执行查询。 有关详细信息,请参阅 sys.dm_exec_query_memory_grants。
- 识别具有sys.dm_exec_requests的特定查询:当前正在执行查询。 有关详细信息,请参阅 sys.dm_exec_requests。
- 使用 sys.dm_exec_query_stats 确定特定查询:查询的历史统计信息。 有关详细信息,请参阅 sys.dm_exec_query_stats。
- 使用查询存储(QDS)和sys.query_store_runtime_stats识别特定查询:QDS 查询的历史统计信息。 有关详细信息,请参阅 sys.query_store_runtime_stats。
聚合内存使用情况统计信息
资源信号灯 DMV sys.dm_exec_query_resource_semaphores
此 DMV 按资源池(内部、默认和用户创建)和 resource_semaphore
(常规和小型查询请求)细分查询预留内存。 有用的查询可能是:
SELECT
pool_id
,total_memory_kb
,available_memory_kb
,granted_memory_kb
,used_memory_kb
,grantee_count, waiter_count
,resource_semaphore_id
FROM sys.dm_exec_query_resource_semaphores rs
以下示例输出显示,22 个请求使用大约 900 MB 的查询执行内存,3 个请求正在等待。 这发生在默认池(pool_id
= 2)和常规查询信号灯(resource_semaphore_id
= 0) 中。
pool_id total_memory_kb available_memory_kb granted_memory_kb used_memory_kb grantee_count waiter_count resource_semaphore_id
------- --------------- ------------------- ----------------- -------------- ------------- ------------ ---------------------
1 30880 30880 0 0 0 0 0
1 5120 5120 0 0 0 0 1
2 907104 0 907104 898656 22 3 0
2 40960 40960 0 0 0 0 1
(4 rows affected)
性能监视计数器
类似的信息可通过性能监视器计数器获得,可在其中观察当前授予的请求(Memory Grants Outstanding
)、等待授予请求(Memory Grants Pending
)和内存授予使用的内存量(Granted Workspace Memory (KB)
)。 在下图中,未完成的授予为 18,挂起的授予为 2,授予的工作区内存为 828,288 KB。 Memory Grants Pending
具有非零值的 Perfmon 计数器指示内存已用尽。
有关详细信息,请参阅 SQL Server 内存管理器对象。
- SQLServer,内存管理器:最大工作区内存(KB)
- SQLServer、内存管理器:未完成内存授予
- SQLServer,内存管理器:内存授予挂起
- SQLServer,内存管理器:授予的工作区内存(KB)
DBCC MEMORYSTATUS
可在其中查看查询预留内存的详细信息的另一个位置是 DBCC MEMORYSTATUS
(查询内存对象部分)。 可以查看用户查询的 Query Memory Objects (default)
输出。 例如,如果启用了名为 PoolAdmin 的资源池的资源调控器,则可以同时查看这两者Query Memory Objects (default)
。Query Memory Objects (PoolAdmin)
下面是系统的示例输出,其中 18 个请求已被授予查询执行内存,2 个请求正在等待内存。 可用计数器为零,表示没有更多的工作区内存可用。 这一事实解释了两个等待的请求。 显示 Wait Time
自请求放入等待队列以来的已用时间(以毫秒为单位)。 有关这些计数器的详细信息,请参阅 查询内存对象。
Query Memory Objects (default) Value
------------------------------------------------------------------------ -----------
Grants 18
Waiting 2
Available 0
Current Max 103536
Future Max 97527
Physical Max 139137
Next Request 5752
Waiting For 8628
Cost 16
Timeout 401
Wait Time 2750
(11 rows affected)
Small Query Memory Objects (default) Value
------------------------------------------------------------------------ -----------
Grants 0
Waiting 0
Available 5133
Current Max 5133
Future Max 5133
DBCC MEMORYSTATUS
还显示有关跟踪查询执行内存的内存 clerk 的信息。 以下输出显示为查询执行(QE)预留分配的页面超过 800 MB。
MEMORYCLERK_SQLQERESERVATIONS (node 0) KB
------------------------------------------------------------------------ -----------
VM Reserved 0
VM Committed 0
Locked Pages Allocated 0
SM Reserved 0
SM Committed 0
Pages Allocated 824640
内存职员 DMV sys.dm_os_memory_clerks
如果需要更多表格结果集,不同于基于 DBCC MEMORYSTATUS
节的结果集,则可以对类似信息使用 sys.dm_os_memory_clerks 。 查找 MEMORYCLERK_SQLQERESERVATIONS
内存职员。 但是,查询内存对象在此 DMV 中不可用。
SELECT type, memory_node_id, pages_kb
FROM sys.dm_os_memory_clerks
WHERE type = 'MEMORYCLERK_SQLQERESERVATIONS'
下面是示例输出:
type memory_node_id pages_kb
----------------------------------------------- -------------- --------------
MEMORYCLERK_SQLQERESERVATIONS 0 824640
MEMORYCLERK_SQLQERESERVATIONS 64 0
使用扩展事件识别内存授予 (XEvents)
有多个扩展事件提供内存授予信息,并允许你通过跟踪捕获此信息:
- sqlserver.additional_memory_grant:在执行期间查询尝试获取更多内存授予时发生。 无法获取此额外的内存授予可能会导致查询速度变慢。
- sqlserver.query_memory_grant_blocking:当查询在等待内存授予时阻止其他查询时发生。
- sqlserver.query_memory_grant_info_sampling:在提供内存授予信息的随机采样查询的末尾发生(例如,可用于遥测)。
- sqlserver.query_memory_grant_resource_semaphores:每个资源调控器资源池的间隔为 5 分钟。
- sqlserver.query_memory_grant_usage:在查询处理结束时发生内存授予超过 5 MB 的查询,让用户知道内存授予不准确之处。
- sqlserver.query_memory_grants:为每个查询以五分钟的间隔进行内存授予。
内存授予反馈扩展事件
有关查询处理内存授予反馈功能的信息,请参阅 内存授予反馈。
- sqlserver.memory_grant_feedback_loop_disabled:禁用内存授予反馈循环时发生。
- sqlserver.memory_grant_updated_by_feedback:通过反馈更新内存授予时发生。
与内存授予相关的查询执行警告
- sqlserver.execution_warning:当 T-SQL 语句或存储过程等待多秒进行内存授予或初始尝试获取内存失败时发生。 将此事件与标识等待的事件结合使用,以排查影响性能的争用问题。
- sqlserver.hash_spill_details:如果内存不足,无法处理哈希联接的生成输入,则发生在哈希处理结束时。 将此事件与任何或
query_pre_execution_showplan
query_post_execution_showplan
事件一起使用来确定生成的计划中哪个操作会导致哈希溢出。 - sqlserver.hash_warning:当内存不足,无法处理哈希联接的生成输入时发生。 这会导致在对生成输入进行分区时出现哈希递归,或者在生成输入分区超过最大递归级别时进行哈希救助。 将此事件与任何或
query_pre_execution_showplan
query_post_execution_showplan
事件一起使用以确定生成的计划中哪个操作导致哈希警告。 - sqlserver.sort_warning:在执行查询上的排序操作不适合内存时发生。 此事件不是针对索引创建导致的排序操作生成的,而仅适用于查询中的排序操作。 (例如,语句中的一个
Order By
Select
。使用此事件可识别由于排序操作而缓慢执行的查询,尤其是当 = 2 时warning_type
,指示需要对数据进行排序的多次传递。
计划生成包含内存授予信息的事件
生成扩展事件的以下查询计划默认包含 granted_memory_kb 和 ideal_memory_kb 字段:
- sqlserver.query_plan_profile
- sqlserver.query_post_execution_plan_profile
- sqlserver.query_post_execution_showplan
- sqlserver.query_pre_execution_showplan
列存储索引生成
通过 XEvents 涵盖的区域之一是列存储生成期间使用的执行内存。 这是可用的事件列表:
- sqlserver.column_store_index_build_low_memory:存储引擎检测到内存不足,行组大小已减小。 此处有几列感兴趣的列。
- sqlserver.column_store_index_build_memory_trace:在索引生成期间跟踪内存使用情况。
- sqlserver.column_store_index_build_memory_usage_scale_down:存储引擎纵向缩减。
- sqlserver.column_store_index_memory_estimation:显示 COLUMNSTORE 行组生成期间的内存估计结果。
标识特定查询
查看单个请求级别时,可能会发现两种类型的查询。 使用大量查询执行内存的查询以及正在等待相同内存的查询。 后一个组可能包含对内存授予的适度需求的请求,如果是这样,你可能会将注意力集中在别处。 但是,如果他们请求巨大的内存大小,他们也可能是罪魁祸首。 如果你发现情况如此,请专注于他们。 通常发现,一个特定的查询是罪犯,但它的许多实例都是催生的。 获取内存授予的实例会导致同一查询的其他实例等待授予。 无论具体情况如何,最终都必须标识所请求的执行内存的查询和大小。
使用 sys.dm_exec_query_memory_grants 标识特定查询
若要查看单个请求及其已授予的内存大小,可以查询 sys.dm_exec_query_memory_grants
动态管理视图。 此 DMV 显示有关当前正在执行的查询的信息,而不是历史信息。
以下语句从 DMV 获取数据,并提取查询文本和查询计划作为结果:
SELECT
session_id
,requested_memory_kb
,granted_memory_kb
,used_memory_kb
,queue_id
,wait_order
,wait_time_ms
,is_next_candidate
,pool_id
,text
,query_plan
FROM sys.dm_exec_query_memory_grants
CROSS APPLY sys.dm_exec_sql_text(sql_handle)
CROSS APPLY sys.dm_exec_query_plan(plan_handle)
下面是活动 QE 内存消耗期间查询的缩写示例输出。 大多数查询都授予了内存,如非 granted_memory_kb
used_memory_kb
NULL 数值所示。 未获得请求的查询正在等待执行内存和 granted_memory_kb
= NULL
执行内存。 此外,它们被置于具有 = queue_id
6 的等待队列中。 他们的 wait_time_ms
指示等待大约37秒。 会话 72 是下一个行获取授权,如 wait_order
= 1 指示,而会话 74 位于其后与 wait_order
= 2。
session_id requested_memory_kb granted_memory_kb used_memory_kb queue_id wait_order wait_time_ms is_next_candidate pool_id
---------- -------------------- -------------------- -------------------- -------- ----------- -------------------- ----------------- -------
80 41232 41232 40848 NULL NULL NULL NULL 2
83 41232 41232 40848 NULL NULL NULL NULL 2
84 41232 41232 40848 NULL NULL NULL NULL 2
74 41232 NULL NULL 6 2 37438 0 2
78 41232 41232 40848 NULL NULL NULL NULL 2
81 41232 41232 40848 NULL NULL NULL NULL 2
71 41232 41232 40848 NULL NULL NULL NULL 2
75 41232 NULL NULL 6 0 37438 1 2
82 41232 41232 40848 NULL NULL NULL NULL 2
76 41232 41232 40848 NULL NULL NULL NULL 2
79 41232 41232 40848 NULL NULL NULL NULL 2
85 41232 41232 40848 NULL NULL NULL NULL 2
70 41232 41232 40848 NULL NULL NULL NULL 2
55 41232 41232 40848 NULL NULL NULL NULL 2
59 41232 NULL NULL 6 3 37438 0 2
62 41232 41232 40848 NULL NULL NULL NULL 2
54 41232 41232 40848 NULL NULL NULL NULL 2
77 41232 41232 40848 NULL NULL NULL NULL 2
52 41232 41232 40848 NULL NULL NULL NULL 2
72 41232 NULL NULL 6 1 37438 0 2
69 41232 41232 40848 NULL NULL NULL NULL 2
73 41232 41232 40848 NULL NULL NULL NULL 2
66 41232 NULL NULL 6 4 37438 0 2
68 41232 41232 40848 NULL NULL NULL NULL 2
63 41232 41232 40848 NULL NULL NULL NULL 2
使用 sys.dm_exec_requests 标识特定查询
SQL Server 中有一种 等待类型 ,指示查询正在等待内存授予 RESOURCE_SEMAPHORE
。 对于单个请求,你可能会看到此等待类型 sys.dm_exec_requests
。 后一个 DMV 是确定哪些查询是授予内存不足的受害者的最佳起点。 还可以将sys.dm_os_wait_stats中的等待观察RESOURCE_SEMAPHORE
为 SQL Server 级别的聚合数据点。 由于其他并发查询已用完内存,无法授予查询内存请求时,将显示此等待类型。 等待请求数高,等待时间长,表示使用执行内存或大型内存请求大小的并发查询过多。
注意
内存授予的等待时间有限。 在过度等待(例如超过 20 分钟)后,SQL Server 将查询超时并引发错误 8645,“等待内存资源执行查询时发生超时。 重新运行查询。”可以通过查看 timeout_sec
sys.dm_exec_query_memory_grants
来查看服务器级别设置的超时值。 超时值在 SQL Server 版本之间可能略有不同。
使用 sys.dm_exec_requests
后,可以看到哪些查询已被授予内存,以及该授予的大小。 此外,还可以通过查找等待类型来确定当前正在等待内存授予的 RESOURCE_SEMAPHORE
查询。 下面是一个查询,其中显示了已授予的请求和等待请求:
SELECT session_id, wait_type, wait_time, granted_query_memory, text
FROM sys.dm_exec_requests
CROSS APPLY sys.dm_exec_sql_text(sql_handle)
WHERE granted_query_memory > 0
OR wait_type = 'RESOURCE_SEMAPHORE'
示例输出显示已授予两个请求内存,另外两十几个请求正在等待授予。 该 granted_query_memory
列以 8 KB 页为单位报告大小。 例如,值为 34,709 表示 34,709 * 8 KB = 277,672 KB 授予的内存。
session_id wait_type wait_time granted_query_memory text
---------- ----------------------------------- -------------------- -------------------------------------------------------------------
65 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
66 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
67 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
68 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
69 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
70 RESOURCE_SEMAPHORE 161435 0 select * from sys.messages order by message_id option (maxdop 1)
71 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
72 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
73 RESOURCE_SEMAPHORE 161435 0 select * from sys.messages order by message_id option (maxdop 1)
74 RESOURCE_SEMAPHORE 161435 0 select * from sys.messages order by message_id option (maxdop 1)
75 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
76 ASYNC_NETWORK_IO 11 34709 select * from sys.messages order by message_id option (maxdop 1)
77 RESOURCE_SEMAPHORE 161435 0 select * from sys.messages order by message_id option (maxdop 1)
78 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
79 RESOURCE_SEMAPHORE 161435 0 select * from sys.messages order by message_id option (maxdop 1)
80 RESOURCE_SEMAPHORE 161435 0 select * from sys.messages order by message_id option (maxdop 1)
81 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
82 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
83 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
84 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
85 ASYNC_NETWORK_IO 14 34709 select * from sys.messages order by message_id option (maxdop 1)
86 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
87 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
88 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
89 RESOURCE_SEMAPHORE 161439 0 select * from sys.messages order by message_id option (maxdop 1)
使用sys.dm_exec_query_stats标识特定查询
如果内存授予问题目前未发生,但想要识别有问题的查询,可以通过查看 sys.dm_exec_query_stats
历史查询数据。 数据的生存期与每个查询的查询计划相关联。 从计划缓存中删除计划时,将从此视图中消除相应的行。 换句话说,DMV 在 SQL Server 重启后或内存压力后不保留的内存中保留统计信息会导致计划缓存释放。 也就是说,你可以在此处找到有价值的信息,特别是用于聚合查询统计信息。 有人可能最近报告了从查询看到大型内存授予,但当你查看服务器工作负荷时,你可能会发现问题已经消失。 在这种情况下, sys.dm_exec_query_stats
可以提供其他 DVM 无法提供的见解。 下面是一个示例查询,可帮助你找到消耗最大执行内存量的前 20 个语句。 即使单个语句的查询结构相同,此输出也会显示各个语句。 例如, SELECT Name FROM t1 JOIN t2 ON t1.Id = t2.Id WHERE t1.Id = 5
是一个单独的行 SELECT Name FROM t1 JOIN t2 ON t1.Id = t2.Id WHERE t1.Id = 100
(只有筛选器谓词值不同)。 查询获取最大授予大小大于 5 MB 的前 20 个语句。
SELECT TOP 20
SUBSTRING(ST.text, (QS.statement_start_offset/2) + 1,
((CASE statement_end_offset
WHEN -1 THEN DATALENGTH(ST.text)
ELSE QS.statement_end_offset END
- QS.statement_start_offset)/2) + 1) AS statement_text
,CONVERT(DECIMAL (10,2), max_grant_kb /1024.0) AS max_grant_mb
,CONVERT(DECIMAL (10,2), min_grant_kb /1024.0) AS min_grant_mb
,CONVERT(DECIMAL (10,2), (total_grant_kb / execution_count) /1024.0) AS avg_grant_mb
,CONVERT(DECIMAL (10,2), max_used_grant_kb /1024.0) AS max_grant_used_mb
,CONVERT(DECIMAL (10,2), min_used_grant_kb /1024.0) AS min_grant_used_mb
,CONVERT(DECIMAL (10,2), (total_used_grant_kb/ execution_count) /1024.0) AS avg_grant_used_mb
,CONVERT(DECIMAL (10,2), (total_ideal_grant_kb/ execution_count) /1024.0) AS avg_ideal_grant_mb
,CONVERT(DECIMAL (10,2), (total_ideal_grant_kb/ 1024.0)) AS total_grant_for_all_executions_mb
,execution_count
FROM sys.dm_exec_query_stats QS
CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) as ST
WHERE max_grant_kb > 5120 -- greater than 5 MB
ORDER BY max_grant_kb DESC
通过查看聚合的 query_hash
查询,可以获得更强大的见解。 此示例演示了如何查找查询语句的所有实例中查询语句的平均、最大值和最小授予大小,因为查询计划首次缓存。
SELECT TOP 20
MAX(SUBSTRING(ST.text, (QS.statement_start_offset/2) + 1,
((CASE statement_end_offset
WHEN -1 THEN DATALENGTH(ST.text)
ELSE QS.statement_end_offset END
- QS.statement_start_offset)/2) + 1)) AS sample_statement_text
,CONVERT(DECIMAL (10,2), SUM(max_grant_kb) /1024.0) AS max_grant_mb
,CONVERT(DECIMAL (10,2), SUM(min_grant_kb) /1024.0) AS min_grant_mb
,CONVERT(DECIMAL (10,2), (SUM(total_grant_kb) / SUM(execution_count)) /1024.0) AS avg_grant_mb
,CONVERT(DECIMAL (10,2), SUM(max_used_grant_kb) /1024.0) AS max_grant_used_mb
,CONVERT(DECIMAL (10,2), SUM(min_used_grant_kb) /1024.0) AS min_grant_used_mb
,CONVERT(DECIMAL (10,2), (SUM(total_used_grant_kb)/ SUM(execution_count)) /1024.0) AS avg_grant_used_mb
,CONVERT(DECIMAL (10,2), (SUM(total_ideal_grant_kb)/ SUM(execution_count)) /1024.0) AS avg_ideal_grant_mb
,CONVERT(DECIMAL (10,2), SUM(total_grant_kb) /1024.0) AS total_grant_all_executions_mb
,SUM(execution_count) AS execution_count
,query_hash
FROM sys.dm_exec_query_stats QS
CROSS APPLY sys.dm_exec_sql_text(QS.sql_handle) as ST
GROUP BY query_hash
HAVING SUM(max_grant_kb) > 5120 -- greater than 5 MB
ORDER BY SUM(max_grant_kb) DESC
OPTION (MAX_GRANT_PERCENT = 5)
该 Sample_Statement_Text
列显示了与查询哈希匹配的查询结构示例,但不应考虑语句中的特定值。 例如,如果语句包含WHERE Id = 5
,则可以以更通用的形式读取它: WHERE Id = @any_value
下面是仅显示所选列的查询的缩写示例输出:
sample_statement_text max_grant_mb avg_grant_mb max_grant_used_mb avg_grant_used_mb avg_ideal_grant_mb total_grant_all_executions_mb execution_count
----------------------------------------- ------------ ------------- ----------------- ----------------- ------------------ ----------------------------- ----------------
select de.ObjectName,de.CounterName,d 282.45 282.45 6.50 6.50 282.45 282.45 1
SELECT SCHEMA_NAME(udf.schema_id) AS [Sch 33.86 8.55 7.80 1.97 8.55 42.74 5
insert into #tmpCounterDateTime (CounterD 32.45 32.45 3.11 3.11 32.45 32.45 1
select db_id() dbid, db_name() dbname, * 20.80 1.30 5.75 0.36 1.30 20.80 16
SELECT SCHEMA_NAME(obj.schema_id) AS [Sch 20.55 5.19 5.13 1.28 5.19 25.93 5
SELECT xmlplan FROM (SELECT ROW_NUMBER() 19.69 1.97 1.09 0.11 1.97 19.69 10
if ( select max(cast(countervalue as floa 16.39 8.20 0.77 0.38 8.20 16.39 2
SELECT udf.name AS [Name], udf.object_id 11.36 5.08 1.66 0.83 5.08 20.33 4
select --* Database_I 10.94 5.47 1.98 0.99 5.47 10.94 2
IF (select max(cast(dat.countervalue as f 8.00 1.00 0.00 0.00 0.53 8.00 8
insert into #tmpCounterDateTime (CounterD 5.72 2.86 1.98 0.99 2.86 5.72 2
INSERT INTO #tmp (CounterDateTime, Counte 5.39 1.08 1.64 0.33 1.08 6.47 6
使用具有sys.query_store_runtime_stats的 查询存储 (QDS) 识别特定查询
如果已启用查询存储,则可以利用其持久化的历史统计信息。 与数据 sys.dm_exec_query_stats
相反,这些统计信息在 SQL Server 重启或内存压力中幸存下来,因为它们存储在数据库中。 QDS 还具有大小限制和保留策略。 有关详细信息,请参阅“设置最佳查询存储捕获模式,并将最相关的数据保留在查询存储部分中的最佳做法中,以管理查询存储。
确定数据库是否已使用此查询启用查询存储:
SELECT name, is_query_store_on FROM sys.databases WHERE is_query_store_on = 1
在要调查的特定数据库的上下文中运行以下诊断查询:
SELECT MAX(qtxt.query_sql_text) AS sample_sql_text ,CONVERT(DECIMAL(10,2), SUM(rts.avg_query_max_used_memory) / 128) AS avg_mem_grant_used_mb ,CONVERT(DECIMAL(10,2), SUM(rts.min_query_max_used_memory) / 128) AS min_mem_grant_used_mb ,CONVERT(DECIMAL(10,2), SUM(rts.max_query_max_used_memory) / 128) AS max_mem_grant_used_mb ,CONVERT(DECIMAL(10,2), SUM(rts.stdev_query_max_used_memory) / 128) AS stdev_mem_grant_used_mb ,CONVERT(DECIMAL(10,2), SUM(rts.last_query_max_used_memory) / 128) AS last_mem_grant_used_mb ,SUM(count_executions) AS count_query_executions FROM sys.query_store_runtime_stats rts JOIN sys.query_store_plan p ON p.plan_id = rts.plan_id JOIN sys.query_store_query q ON p.query_id = q.query_id LEFT OUTER JOIN sys.query_store_query_text qtxt ON q.query_text_id = qtxt.query_text_id GROUP BY q.query_hash HAVING SUM(rts.avg_query_max_used_memory) /128 > 5 -- greater than 5 MB ORDER BY SUM(avg_query_max_used_memory) DESC OPTION (MAX_GRANT_PERCENT = 5)
此处的原则与
sys.dm_exec_query_stats
语句的聚合统计信息相同。 但是,一个区别在于,对于 QDS,你只查看此数据库范围内的查询,而不是整个 SQL Server。 因此,可能需要知道执行特定内存授予请求的数据库。 否则,请在多个数据库中运行此诊断查询,直到找到相当大的内存授予。下面是缩写的示例输出:
sample_sql_text avg_mem_grant_used_mb min_mem_grant_used_mb max_mem_grant_used_mb stdev_mem_grant_used_mb last_mem_grant_used_mb count_query_executions ----------------------------------------- ---------------------- ---------------------- ---------------------- ------------------------ ----------------------- ---------------------- SELECT qtxt.query_sql_text ,CONVERT(D 550.16 550.00 550.00 0.00 550.00 1 SELECT qtxt.query_sql_text ,rts.avg_q 61.00 36.00 65.00 10.87 51.00 14 SELECT qtxt.query_sql_text ,q.* ,rts 25.46 25.00 25.00 0.00 25.00 2 insert into #tmpStats select 5 'Database 13.69 13.00 13.00 0.03 13.00 16 SELECT q.* ,rts 11.93 11.00 12.00 0.23 12.00 2 SELECT * ,rts.avg_query_max_used_memory 9.70 9.00 9.00 0.00 9.00 1 SELECT qtxt.query_sql_text ,rts.avg_q 9.32 9.00 9.00 0.00 9.00 1 select db_id() dbid, db_name() dbname, * 7.33 7.00 7.00 0.00 7.00 9 SELECT q.* ,rts.avg_query_max_used_memo 6.65 6.00 6.00 0.00 6.00 1 (@_msparam_0 nvarchar(4000),@_msparam_1 5.17 4.00 5.00 0.68 4.00 2
自定义诊断查询
下面是一个查询,用于合并来自多个视图的数据,包括前面列出的三个。 除了服务器sys.dm_exec_query_resource_semaphores
级统计信息外,它还提供了会话及其授予的sys.dm_exec_requests
sys.dm_exec_query_memory_grants
更深入的视图。
注意
此查询将返回每个会话的两行,因为使用 sys.dm_exec_query_resource_semaphores
(一行用于常规资源信号量,另一行用于小型查询资源信号量)。
SELECT CONVERT (varchar(30), GETDATE(), 121) as runtime
, r.session_id
, r.wait_time
, r.wait_type
, mg.request_time
, mg.grant_time
, mg.requested_memory_kb
/ 1024 requested_memory_mb
, mg.granted_memory_kb
/ 1024 AS granted_memory_mb
, mg.required_memory_kb
/ 1024 AS required_memory_mb
, max_used_memory_kb
/ 1024 AS max_used_memory_mb
, rs.pool_id as resource_pool_id
, mg.query_cost
, mg.timeout_sec
, mg.resource_semaphore_id
, mg.wait_time_ms AS memory_grant_wait_time_ms
, CASE mg.is_next_candidate
WHEN 1 THEN 'Yes'
WHEN 0 THEN 'No'
ELSE 'Memory has been granted'
END AS 'Next Candidate for Memory Grant'
, r.command
, ltrim(rtrim(replace(replace (substring (q.text, 1, 1000), char(10), ' '), char(13), ' '))) [text]
, rs.target_memory_kb
/ 1024 AS server_target_grant_memory_mb
, rs.max_target_memory_kb
/ 1024 AS server_max_target_grant_memory_mb
, rs.total_memory_kb
/ 1024 AS server_total_resource_semaphore_memory_mb
, rs.available_memory_kb
/ 1024 AS server_available_memory_for_grants_mb
, rs.granted_memory_kb
/ 1024 AS server_total_granted_memory_mb
, rs.used_memory_kb
/ 1024 AS server_used_granted_memory_mb
, rs.grantee_count AS successful_grantee_count
, rs.waiter_count AS grant_waiters_count
, rs.timeout_error_count
, rs.forced_grant_count
, mg.dop
, r.blocking_session_id
, r.cpu_time
, r.total_elapsed_time
, r.reads
, r.writes
, r.logical_reads
, r.row_count
, s.login_time
, d.name
, s.login_name
, s.host_name
, s.nt_domain
, s.nt_user_name
, s.status
, c.client_net_address
, s.program_name
, s.client_interface_name
, s.last_request_start_time
, s.last_request_end_time
, c.connect_time
, c.last_read
, c.last_write
, qp.query_plan
FROM sys.dm_exec_requests r
INNER JOIN sys.dm_exec_connections c
ON r.connection_id = c.connection_id
INNER JOIN sys.dm_exec_sessions s
ON c.session_id = s.session_id
INNER JOIN sys.databases d
ON r.database_id = d.database_id
INNER JOIN sys.dm_exec_query_memory_grants mg
ON s.session_id = mg.session_id
INNER JOIN sys.dm_exec_query_resource_semaphores rs
ON mg.resource_semaphore_id = rs.resource_semaphore_id
CROSS APPLY sys.dm_exec_sql_text (r.sql_handle ) AS q
CROSS APPLY sys.dm_exec_query_plan(mg.plan_handle) qp
OPTION (MAXDOP 1, LOOP JOIN )
注意
此 LOOP JOIN
诊断查询中使用提示以避免查询本身授予内存,并且不使用 ORDER BY
子句。 如果诊断查询最终等待授予本身,则诊断内存授予的目的将会失败。 提示 LOOP JOIN
可能会导致诊断查询变慢,但在这种情况下,获取诊断结果更为重要。
下面是此诊断查询中仅包含所选列的缩写示例输出。
session_id | wait_time | wait_type | requested_memory_mb | granted_memory_mb | required_memory_mb | max_used_memory_mb | resource_pool_id |
---|---|---|---|---|---|---|---|
60 | 0 | Null | 9 | 9 | 7 | 1 | 1 |
60 | 0 | Null | 9 | 9 | 7 | 1 | 2 |
75 | 1310085 | RESOURCE_SEMAPHORE | 40 | Null | 0 | Null | 1 |
75 | 1310085 | RESOURCE_SEMAPHORE | 40 | Null | 0 | Null | 2 |
86 | 1310129 | RESOURCE_SEMAPHORE | 40 | Null | 0 | Null | 1 |
86 | 1310129 | RESOURCE_SEMAPHORE | 40 | Null | 0 | Null | 2 |
示例输出清楚地说明了 = 60 提交的 session_id
查询如何成功获得请求的 9 MB 内存授予,但成功启动查询执行只需要 7 MB。 最后,查询只使用了从服务器收到的 9 MB 中的 1 MB。 输出还显示会话 75 和 86 正在等待内存授予,因此。RESOURCE_SEMAPHORE
wait_type
他们的等待时间已经超过1,300秒(21分钟),他们的 granted_memory_mb
是 NULL
。
此诊断查询是一个示例,因此可以随意根据需要以任何方式对其进行修改。 此查询的版本也用于Microsoft SQL Server 支持的诊断工具中使用。
诊断工具
有一些诊断工具Microsoft SQL Server 技术支持用于收集日志并更有效地排查问题。 SQL LogScout 和 Pssdiag 配置管理器(以及 SQLDiag)收集前面所述的 DMV 和性能监视器计数器的输出,这些计数器可以帮助你诊断内存授予问题。
如果使用 LightPerf、GeneralPerf 或 DetailedPerf 方案运行 SQL LogScout,该工具将收集必要的日志。 然后,可以手动检查 YourServer_PerfStats.out 并查找 -- dm_exec_query_resource_semaphores --
和 -- dm_exec_query_memory_grants --
输出。 或者,可以使用 SQL Nexus 将来自 SQL LogScout 或 PSSDIAG 的输出导入到 SQL Server 数据库中,而不是手动检查。 SQL Nexus 创建两个表, tbl_dm_exec_query_resource_semaphores
其中包含 tbl_dm_exec_query_memory_grants
诊断内存授予所需的信息。 SQL LogScout 和 PSSDIAG 还以 Perfmon 日志的形式收集 Perfmon 日志。BLG 文件,可用于查看性能监视器计数器部分中所述的性能计数器。
为什么内存授予对开发人员或 DBA 很重要
根据Microsoft支持体验,内存授予问题往往是一些最常见的与内存相关的问题。 应用程序通常执行看似简单的查询,这些查询最终可能会导致 SQL Server 上的性能问题,因为执行大量排序或哈希操作。 此类查询不仅消耗大量 SQL Server 内存,还会导致其他查询等待内存可用,因此性能瓶颈。
使用此处概述的工具(DMV、Perfmon 计数器和实际查询计划),可以识别哪些查询是大型授权使用者。 然后,可以优化或重写这些查询,以解决或减少工作区内存使用量。
开发人员可以对排序和哈希操作执行的操作
确定使用大量查询预留内存的特定查询后,可以通过重新设计这些查询来采取措施来减少内存授予。
导致查询中的排序和哈希操作的原因
第一步是了解查询中的哪些操作可能导致内存授予。
查询使用 SORT 运算符的原因:
ORDER BY (T-SQL) 导致行在作为最终结果流式传输之前进行排序。
如果基础索引不存在对分组列进行排序,GROUP BY (T-SQL) 可能会在查询计划中引入排序运算符。
DISTINCT (T-SQL) 的行为类似于
GROUP BY
. 若要标识不同的行,将排序中间结果,然后删除重复项。 如果由于索引查找或扫描而尚未对数据进行排序,则优化器会使用此Sort
运算符。在查询优化器选择时,合并联接运算符要求对两个联接的输入进行排序。 如果聚集索引在其中一个表中的联接列上不可用,SQL Server 可能会触发排序。
查询使用 HASH 查询计划运算符的原因:
此列表并不详尽,但包括哈希操作最常见的原因。 分析查询计划 以标识哈希匹配操作。
JOIN (T-SQL):联接表时,SQL Server 可以选择三个物理运算符,
Nested Loop
Merge Join
以及Hash Join
。 如果 SQL Server 最终选择了 哈希联接,则需要 QE 内存来存储和处理中间结果。 通常,缺少良好的索引可能会导致这种资源开销最高的联接运算符Hash Join
。 若要检查要识别Hash Match
的查询计划,请参阅逻辑运算符和物理运算符参考。DISTINCT (T-SQL):
Hash Aggregate
运算符可用于消除行集中的重复项。 若要在查询计划中查找 (Hash Match
Aggregate
),请参阅逻辑运算符和物理运算符参考。UNION (T-SQL):这类似于
DISTINCT
。 AHash Aggregate
可用于删除此运算符的重复项。SUM/AVG/MAX/MIN (T-SQL):任何聚合操作都可以作为一种
Hash Aggregate
执行。 若要在查询计划中查找 (Hash Match
Aggregate
),请参阅逻辑运算符和物理运算符参考。
了解这些常见原因有助于尽可能消除传入 SQL Server 的大型内存授予请求。
减少排序和哈希操作或授予大小的方法
- 使统计信息保持最新。 这一基本步骤可提高许多级别的查询性能,可确保在选择查询计划时查询优化器具有最准确的信息。 SQL Server 根据统计信息确定为其内存授予请求的大小。 过时的统计信息可能会导致过度估计或低估授予请求,从而导致不必要的高授予请求或将结果溢出到磁盘。 确保在 数据库中启用自动更新统计信息 ,并/或使用 UPDATE STATISTICS 或 sp_updatestats保持静态更新。
- 减少来自表的行数。 如果使用限制性更高的 WHERE 筛选器或 JOIN 并减少行数,查询计划中后续的排序将排序或聚合较小的结果集。 较小的中间结果集需要更少的工作集内存。 这是一个常规规则,开发人员不仅可以遵循保存工作集内存,还可以减少 CPU 和 I/O(此步骤并不总是可能的)。 如果编写良好且资源高效的查询已到位,则已满足此准则。
- 在联接列上创建索引以帮助合并联接。 查询计划中的中间操作受基础表上的索引影响。 例如,如果表对联接列没有索引,并且合并联接是最具成本效益的联接运算符,则执行联接之前,必须对该表中的所有行进行排序。 如果列上存在索引,则可以消除排序操作。
- 创建索引以帮助避免哈希操作。 通常,基本查询优化首先检查查询是否具有适当的索引,以帮助他们减少读取,并尽可能减少或消除大型排序或哈希操作。 通常选择哈希联接来处理大型、未排序和非索引输入。 创建索引可能会更改此优化器策略并加快数据检索速度。 有关创建索引的帮助,请参阅数据库引擎优化顾问和优化缺少索引建议的非聚集索引。
- 在适合使用
GROUP BY
聚合查询的情况下使用 COLUMNSTORE 索引。 处理非常大的行集并通常执行“分组依据”聚合的分析查询可能需要大型内存区块才能完成工作。 如果索引不可用,则提供有序结果,查询计划中会自动引入排序。 一种非常大的结果可能会导致昂贵的内存授予。 - 如果不需要,
ORDER BY
请将其删除。 如果结果以自己的方式对结果进行排序或允许用户修改查看的数据的顺序,则无需在 SQL Server 端执行排序。 只需按照服务器生成数据的顺序将数据流式传输到应用程序,让最终用户自行对其进行排序。 Power BI 或 Reporting Services 等报表应用程序是允许最终用户对数据进行排序的此类应用程序的示例。 - 请注意,尽管谨慎,但在 T-SQL 查询中存在联接时使用 LOOP JOIN 提示。 此方法可以避免使用内存授予的哈希或合并联接。 但是,仅建议使用此选项作为最后手段,因为强制联接可能会导致查询速度明显降低。 压力测试工作负荷,以确保这是一个选项。 在某些情况下,嵌套循环联接甚至可能不是一个选项。 在这种情况下,SQL Server 可能会失败并出现错误MSSQLSERVER_8622,“由于此查询中定义的提示,查询处理器无法生成查询计划。
内存授予查询提示
自 SQL Server 2012 SP3 以来,已存在一个查询提示,可用于控制每个查询的内存授予大小。 下面是有关如何使用此提示的示例:
SELECT Column1, Column2
FROM Table1
ORDER BY Column1
OPTION (MIN_GRANT_PERCENT = 3, MAX_GRANT_PERCENT = 5 )
建议在此处使用保守值,尤其是在预期并发执行查询的多个实例的情况下。 确保对工作负荷进行压力测试,以匹配生产环境并确定要使用的值。
有关详细信息,请参阅 MAX_GRANT_PERCENT和MIN_GRANT_PERCENT。
资源调控器
QE 内存是资源调控器在使用MIN_MEMORY_PERCENT和MAX_MEMORY_PERCENT设置时实际限制的内存。 确定导致大量内存授予的查询后,可以限制会话或应用程序使用的内存。 值得一提的是, default
工作负荷组允许查询占用最多 25% 的内存,这些内存可以授予 SQL Server 实例。 有关详细信息,请参阅 资源调控器资源池 和 CREATE WORKLOAD GROUP。
自适应查询处理和内存授予反馈
SQL Server 2017 引入了内存授予反馈功能。 它允许查询执行引擎根据以前的历史记录调整提供给查询的授予。 目标是尽可能减小授予的大小,或在需要更多内存时增加它。 此功能已在三波中发布:
- SQL Server 2017 中的批处理模式内存授予反馈
- SQL Server 2019 中的行模式内存授予反馈
- 使用 SQL Server 2022 中的查询存储和百分位授予的磁盘上的内存授予反馈
有关详细信息,请参阅内存授予反馈。 内存授予功能可能会减少执行时查询的内存授予大小,从而减少来自大型授予请求的问题。 此功能已到位,尤其是在 SQL Server 2019 及更高版本上,其中行模式自适应处理可用,甚至可能不会注意到查询执行中出现的任何内存问题。 但是,如果此功能已到位(默认启用),但仍会看到较大的 QE 内存消耗,请应用前面讨论的步骤来重写查询。
增加 SQL Server 或 OS 内存
执行步骤来减少查询的不必要的内存授予后,如果仍遇到相关的内存不足问题,则工作负荷可能需要更多内存。 因此,如果系统上有足够的物理内存,请考虑使用 max server memory
设置增加 SQL Server 的内存。 请遵循有关为 OS 和其他需求留下约 25% 内存的建议。 有关详细信息,请参阅 服务器内存配置选项。 如果系统上没有可用的足够内存,请考虑添加物理 RAM,或者如果它是虚拟机,请增加 VM 的专用 RAM。
内存授予内部
若要了解有关查询执行内存的某些内部信息的详细信息,请参阅 了解 SQL Server 内存授予 博客文章。
如何创建具有大量内存授予使用情况的性能方案
最后,以下示例演示了如何模拟查询执行内存的大规模消耗,以及如何引入等待的 RESOURCE_SEMAPHORE
查询。 可以执行此操作,了解如何使用本文中所述的诊断工具和技术。
警告
请勿在生产系统上使用此功能。 提供此模拟,可帮助你了解概念并帮助你更好地了解它。
在测试服务器上,安装 RML 实用工具 和 SQL Server。
使用 SQL Server Management Studio 等客户端应用程序将 SQL Server 的最大服务器内存设置降低到 1,500 MB:
EXEC sp_configure 'max server memory', 1500 RECONFIGURE
打开命令提示符并将目录更改为 RML 实用工具文件夹:
cd C:\Program Files\Microsoft Corporation\RMLUtils
使用 ostress.exe 针对测试 SQL Server 生成多个同时请求。 此示例使用 30 个同时会话,但可以更改该值:
ostress.exe -E -S. -Q"select * from sys.messages order by message_id option (maxdop 1)" -n30
使用前面所述的诊断工具确定内存授予问题。
处理大型内存授予的方法摘要
- 重写查询。
- 更新统计信息并定期更新统计信息。
- 为标识的查询或查询创建适当的索引。 索引可能会减少处理的大量行,从而更改
JOIN
算法并减小授予大小或完全消除这些行。 OPTION
使用 (min_grant_percent = XX,max_grant_percent = XX) 提示。- 使用 资源调控器。
- SQL Server 2017 和 2019 使用自适应查询处理,允许内存授予反馈机制在运行时动态调整内存授予大小。 此功能可能首先阻止内存授予问题。
- 增加 SQL Server 或 OS 内存。