排查 SQL Server 中的内存不足问题
现象
SQL Server 使用与复杂和丰富的功能集相对应的复杂 内存体系结构 。 由于内存需求多样,内存消耗和内存压力可能有很多,最终导致内存不足的情况。
有一些常见错误指示 SQL Server 中的内存不足。 错误示例包括:
- 701:未能分配足够的内存来运行查询。
- 802:无法获取内存来分配缓冲池中的页(数据或索引页)。
- 1204:无法为锁分配内存。
- 6322:无法为 XML 分析器分配内存。
- 6513:由于内存压力,无法初始化 CLR。
- 6533:AppDomain 由于内存不足而卸载。
- 8318:由于内存不足,无法加载 SQL 性能计数器。
- 8356 或 8359:由于内存不足,ETW 或 SQL 跟踪无法运行。
- 8556:由于内存不足而无法加载 MSDTC。
- 8645:由于内存授予(排序和哈希)没有内存,无法执行查询,有关详细信息,请参阅 如何排查 SQL Server 错误 8645。
- 8902:在 DBCC 执行期间未能分配内存。
- 9695 或 9696:无法为 Service Broker 操作分配内存。
- 17131 或 17132:由于内存不足,服务器启动失败。
- 17890:由于 OS 正在分页的 SQL 内存,无法分配内存。
- 18053:错误以 terse 模式打印,因为在格式设置过程中出错。 跟踪、ETW 和通知等均被跳过。
- 22986 或 22987:由于内存不足而更改数据捕获失败。
- 25601:Xevent 引擎内存不足。
- 26053:由于内存不足,SQL 网络接口无法初始化。
- 30085、30086、30094:SQL 全文操作由于内存不足而失败。
原因
许多因素可能会导致内存不足。 这些因素包括操作系统设置、物理内存可用性、使用 SQL Server 中的内存的组件以及当前工作负荷的内存限制。 在大多数情况下,由于内存不足错误而失败的查询不是此错误的原因。 总的来说,原因可分为三类:
原因 1:外部或 OS 内存压力
外部压力是指进程外部的组件的内存利用率较高,导致 SQL Server 内存不足。 必须了解系统上的其他应用程序是否消耗内存并导致内存不足。 SQL Server 是少数旨在通过减少内存使用来响应 OS 内存压力的应用程序之一。 这意味着,如果应用程序或驱动程序请求内存,OS 将向所有应用程序发送一个信号来释放内存,SQL Server 将通过减少其自己的内存使用量来响应。 很少有其他应用程序会做出响应,因为它们未设计为侦听该通知。 因此,如果 SQL Server 开始削减其内存使用量,则会减少其内存池,并且无论哪个组件需要内存,都可能无法获取内存。 因此,你开始收到 701 或其他与内存相关的错误。 有关 SQL 如何动态分配和释放内存的详细信息,请参阅 SQL Server 内存体系结构。 有关问题的更详细的诊断和解决方案,请参阅 本文中的外部内存压力 。
有三大类问题可能导致 OS 内存压力:
- 应用程序相关问题:一个或多个应用程序一起耗尽可用的物理内存。 OS 将通过尝试释放一些内存来响应资源的新应用程序请求。 常见方法是查找哪些应用程序耗尽内存,并采取必要的步骤来平衡内存之间的内存,而不会导致 RAM 耗尽。
- 设备驱动程序问题:如果驱动程序调用内存分配函数错误,则设备驱动程序可能会导致所有进程的工作集分页。
- 操作系统产品问题。
有关这些步骤和故障排除步骤的详细说明,请参阅 MSSQLSERVER_17890。
原因 2:内部内存压力,而不是来自 SQL Server
内部内存压力是指由 SQL Server 进程内部的因素导致的低内存可用性。 SQL Server 进程内可能运行的一些组件是 SQL Server 引擎的“外部”。 示例包括 OLE DB 提供程序(DLL),例如链接服务器、SQLCLR 过程或函数、扩展过程(XP)和 OLE 自动化(sp_OA*
)。 其他示例包括防病毒或其他安全程序,它们将 DLL 注入进程中以用于监视目的。 这些组件中的任何组件出现问题或设计不佳都可能导致消耗大量内存。 例如,假设链接服务器将 2000 万行数据从外部源缓存到 SQL Server 内存中。 就 SQL Server 而言,内存分配器不会报告高内存使用率,但 SQL Server 进程内部消耗的内存将会很高。 例如,来自链接服务器 DLL 的此内存增长会导致 SQL Server 开始削减其内存使用量(见上图),并将为 SQL Server 中的组件创建低内存条件,从而导致内存不足错误。 有关此问题的更详细的诊断和解决方案,请参阅 内部内存压力,而不是来自 SQL Server。
注意
SQL Server 进程空间(例如 MSOLEDBSQL、 SQL Native Client)中使用的一些Microsoft DLL 能够与 SQL Server 内存基础结构进行交互,以便进行报告和分配。 可以运行 select * from sys.dm_os_memory_clerks where type='MEMORYCLERK_HOST'
以获取它们的列表,并跟踪部分分配的内存消耗量。
原因 3:来自 SQL Server 组件的内部内存压力
来自 SQL Server 引擎内部组件的内部内存压力也可能导致内存不足错误。 通过内存职员跟踪的数百个组件在 SQL Server 中分配内存。 必须确定哪些内存职员负责最大的内存分配来解决此问题。 例如,如果发现 OBJECTSTORE_LOCK_MANAGER
内存职员显示大型内存分配,则需要了解锁管理器消耗这么多内存的原因。 你可能会发现存在获取许多锁的查询。 可以使用索引来优化这些查询,缩短长时间保留锁的任何事务,或者检查是否禁用锁升级。 每个内存分配器或组件都有一种独特的方式来访问和使用内存。 有关详细信息,请参阅内存分配器类型及其说明。 有关此问题的更详细的诊断和解决方案,请参阅 SQL Server 引擎的内部内存使用情况。
内存压力类型的可视表示形式
下图演示了可能导致 SQL Server 内存不足的情况的压力类型:
用于收集故障排除数据的诊断工具
可以使用以下诊断工具收集故障排除数据:
性能监视器
使用性能监视器配置和收集以下计数器:
- Memory:Available MBytes
- Process:Working Set
- Process:Private Bytes
- SQL Server:Memory Manager: (all counters)
- SQL Server:Buffer Manager: (all counters)
DMV 或 DBCC MEMORYSTATUS
可以使用 sys.dm_os_memory_clerks 或 DBCC MEMORYSTATUS 来观察 SQL Server 内的总体内存使用率。
SSMS 中的内存消耗标准报表
在 SQL Server Management Studio 中查看内存使用情况:
- 启动 SQL Server Management Studio,连接到一个服务器。
- 在对象资源管理器中,右键单击 SQL Server 实例名称。
- 在上下文菜单中,选择“ 报告>标准报表>内存消耗”。
PSSDiag 或 SQL LogScout
捕获这些数据点的替代方法是使用 PSSDiag 或 SQL LogScout 等工具。
如果使用 PSSDiag,请将其配置为捕获 Perfmon 收集器和 自定义诊断\SQL 内存错误 收集器。
如果使用 SQL LogScout,请将其配置为捕获 内存 方案。
以下部分介绍每个方案(外部或内部内存压力)的更详细步骤。
故障排除方法
如果内存不足错误偶尔出现或短暂出现,则可能存在一个生存期较短的内存问题,可以自行解决。 在这些情况下,你可能不需要采取任何措施。 但是,如果在多个连接上多次发生错误,并且持续数秒或更长时间,请按照以下部分中的诊断和解决方案进一步排查内存错误。
外部内存压力
若要诊断 SQL Server 进程外部系统上内存不足的情况,请使用以下方法:
收集性能监视器计数器。 通过查看这些计数器,调查除 SQL Server 之外的应用程序或服务是否正在消耗此服务器上的内存:
- Memory:Available MBytes
- Process:Working Set
- Process:Private Bytes
下面是使用 PowerShell 的 Perfmon 日志收集的示例:
clear $serverName = $env:COMPUTERNAME $Counters = @( ("\\$serverName" +"\Memory\Available MBytes"), ("\\$serverName" +"\Process(*)\Working Set"), ("\\$serverName" +"\Process(*)\Private Bytes") ) Get-Counter -Counter $Counters -SampleInterval 2 -MaxSamples 1 | ForEach-Object { $_.CounterSamples | ForEach-Object { [pscustomobject]@{ TimeStamp = $_.TimeStamp Path = $_.Path Value = ([Math]::Round($_.CookedValue, 3)) } } }
查看系统事件日志并查找内存相关错误(例如,虚拟内存不足)。
查看应用程序事件日志,了解与应用程序相关的内存问题。
下面是一个 PowerShell 脚本示例,用于查询关键字“memory”的系统和应用程序事件日志。可以随意使用其他字符串(例如“resource”)进行搜索:
Get-EventLog System -ComputerName "$env:COMPUTERNAME" -Message "*memory*" Get-EventLog Application -ComputerName "$env:COMPUTERNAME" -Message "*memory*"
解决不太关键的应用程序或服务的任何代码或配置问题,以减少其内存使用量。
如果 SQL Server 以外的应用程序占用资源,请尝试停止或重新安排这些应用程序,或考虑在单独的服务器上运行它们。 这些步骤将消除外部内存压力。
内部内存压力,不来自 SQL Server
若要诊断 SQL Server 内部模块(DLL)引起的内部内存压力,请使用以下方法:
如果 SQL Server 不使用内存中的锁定页(AWE API),则其大部分内存反映在 性能监视器 中的 Process:Private Bytes 计数器(
SQLServr
实例)。 来自 SQL Server 引擎内的总体内存使用量反映在 SQL Server:内存管理器:服务器内存总量(KB)计数器中。 如果发现值 Process:Private Bytes 和 SQL Server:Memory Manager:Total Server Memory (KB)之间存在显著差异,则此差异可能来自 DLL(链接服务器、XP、SQLCLR 等)。 例如,如果 专用字节 为 300 GB,并且 服务器总内存 为 250 GB,则进程中大约 50 GB 的总内存来自 SQL Server 引擎外部。如果 SQL Server 在内存中使用锁定页(AWE API),则识别问题更具挑战性,因为性能监视器不提供跟踪单个进程的内存使用情况的 AWE 计数器。 SQL Server 引擎内的总体内存使用率反映在 SQL Server:内存管理器:服务器内存总量(KB)计数器中。 典型的“Process:Private Bytes”值可能在 300 MB 和 1-2 GB 之间变化。 如果发现进程:专用字节的显著用法超出了此典型用途,则差异可能来自 DLL(链接服务器、XP、SQLCLR 等)。 例如,如果 专用字节 计数器为 4-5 GB,并且 SQL Server 正在使用内存中的锁定页(AWE),则大部分专用字节可能来自 SQL Server 引擎外部。 这是一种近似技术。
使用任务列表实用工具确定 SQL Server 空间中加载的所有 DLL:
tasklist /M /FI "IMAGENAME eq sqlservr.exe"
还可以使用以下查询来检查加载的模块(DLL),并查看是否有任何意外的模块。
SELECT * FROM sys.dm_os_loaded_modules
如果怀疑链接服务器模块导致大量内存消耗,可以通过禁用 “允许进程 内”选项将其配置为进程不足。 有关详细信息,请参阅创建链接服务器。 并非所有链接服务器 OLE DB 提供程序都已用完进程。 有关详细信息,请联系产品制造商。
在极少数情况下,使用 OLE 自动化对象(
sp_OA*
)时,可以通过仅指定上下文值 4 (本地(.exe) OLE 服务器,将对象配置为在 SQL Server 外部的进程中运行。 有关详细信息,请参阅 sp_OACreate。
SQL Server 引擎的内部内存使用情况
若要诊断来自 SQL Server 引擎内部组件的内部内存压力,请使用以下方法:
开始收集 SQL Server 的性能监视器计数器:SQL Server:Buffer Manager 和 SQL Server:Memory Manager。
多次查询 SQL Server 内存分配器 DMV,查看引擎中内存消耗最大的位置:
SELECT pages_kb, type, name, virtual_memory_committed_kb, awe_allocated_kb FROM sys.dm_os_memory_clerks ORDER BY pages_kb DESC
或者,可以在看到这些错误消息时观察更详细的
DBCC MEMORYSTATUS
输出及其更改方式。DBCC MEMORYSTATUS
如果在内存分配器中发现了明显的导致错误的因素,请专注于解决该组件的内存消耗具体问题。 下面是几个示例:
- 如果内存 clerk
MEMORYCLERK_SQLQERESERVATIONS
占用内存,请识别使用巨额内存授予的查询,并通过索引对其进行优化、重写(例如删除ORDER by
)或应用内存授予查询提示(请参阅 min_grant_percent和max_grant_percent提示 )。 还可以 创建资源调控器池 来控制内存授予内存的使用情况。 有关内存授予的详细信息,请参阅 排查 SQL Server 中内存授予导致性能缓慢或内存不足的问题。 - 如果缓存了大量即席查询计划,则
CACHESTORE_SQLCP
内存职员将使用大量的内存。 识别无法重复使用其查询计划的非参数化查询,并通过转换为存储过程、使用sp_executesql
或使用FORCED
参数化来参数化它们。 如果已启用 跟踪标志 174,可以禁用它以查看此问题是否解决了问题。 - 如果对象计划缓存存储
CACHESTORE_OBJCP
消耗过多的内存,请确定哪些存储过程、函数或触发器正在使用大量内存,并可能重新设计应用程序。 通常,这可能是由于大量的数据库或架构,每个数据库或架构中都有数百个过程。 OBJECTSTORE_LOCK_MANAGER
如果内存 clerk 显示大型内存分配,请识别应用多个锁的查询,并使用索引对其进行优化。 缩短导致锁定在特定隔离级别长时间未释放的事务,或检查锁定升级是否被禁用。- 如果观察到非常大
TokenAndPermUserStore
(),可以使用跟踪标志 4618 来select type, name, pages_kb from sys.dm_os_memory_clerks where name = 'TokenAndPermUserStore'
限制缓存的大小。 - 如果观察到内存中 OLTP 来自内存职员的
MEMORYCLERK_XTP
内存问题,可以参考 内存中 OLTP 和 内存优化 tempdb 元数据(HkTempDB)内存不足错误的监视和故障排除。
- 如果内存 clerk
可能使内存可用的快速缓解措施
以下操作可能会释放一些内存,并使它可供 SQL Server 使用:
更改内存配置设置
检查以下 SQL Server 内存配置参数,并在可能的情况下考虑增加最大服务器内存:
- max server memory
- min server memory
注意
如果发现异常设置,请根据需要更正这些设置,并考虑到内存需求增加的原因。 服务器内存配置选项中列出了默认设置。
如果尚未配置 最大服务器内存(尤其是内存中的锁定页),请考虑将其设置为特定值以允许 OS 的某些内存。 请参阅“内存服务器配置中的锁定页面”选项。
更改或将工作负荷移出系统
调查查询工作负荷:并发会话数,当前正在执行查询,并查看是否有不太重要的应用程序可能暂时停止或移动到另一个 SQL Server。
对于只读工作负荷,请考虑将它们移动到 Always On 环境中的只读次要副本。 有关详细信息,请参阅 将只读工作负荷卸载到 AlwaysOn 可用性组 的次要副本,并 配置对 AlwaysOn 可用性组次要副本的只读访问权限。
确保虚拟机的内存配置正确
如果在虚拟机 (VM) 上运行 SQL Server,请确保该 VM 的内存未过量使用。 有关如何为 VM 配置内存的想法,请参阅 虚拟化 - 过度使用内存以及如何在 VM 中检测内存以及如何 对 ESX/ESXi 虚拟机性能问题(内存过度使用)进行故障排除。
释放 SQL Server 中的内存
可以运行以下一个或多个 DBCC 命令来释放多个 SQL Server 内存缓存:
DBCC FREESYSTEMCACHE
DBCC FREESESSIONCACHE
DBCC FREEPROCCACHE
重启 SQL Server 服务
在某些情况下,如果需要处理内存严重耗尽且 SQL Server 无法处理查询,可以考虑重启服务。
考虑针对特定方案使用资源调控器
如果使用资源调控器,建议检查资源池和工作负荷组设置,以查看它们是否不会太严重地限制内存。
在物理或虚拟服务器上添加更多 RAM
如果问题仍然存在,则需要进一步调查并可能增加服务器资源(RAM)。