排查 SQL Server 中的内存耗尽或内存不足问题

总结

本文介绍如何排查SQL Server内存问题,包括内存不足和分配内存错误失败、内存不足情况和相关性能问题。 它解释了这些症状、内存压力的三个主要类别(来自非引擎模块的外部、来自非引擎模块的内部以及来自SQL Server引擎组件的内部)、可用于收集数据的诊断工具,以及可以采取的步骤来修复或缓解SQL Server实例上的内存压力。

内存不足错误的症状

SQL Server 使用与复杂和丰富的功能集相对应的复杂 内存体系结构 。 由于内存需求多样,许多源可能会导致内存消耗和内存压力,从而导致内存不足的情况。

常见错误表示SQL Server内存不足。 这些错误的示例包括:

  • 701:未能分配足够的内存来运行查询。
  • 802:无法获取内存来分配缓冲池中的页(数据或索引页)。
  • 1204:无法为锁分配内存。
  • 6322:无法为 XML 分析器分配内存。
  • 6513:由于内存压力,无法初始化 CLR。
  • 6533:AppDomain 由于内存不足而卸载。
  • 8318:由于内存不足,无法加载 SQL 性能计数器。
  • 8356 或 8359:由于内存不足,ETW 或 SQL 跟踪无法运行。
  • 8556:由于内存不足而无法加载 MSDTC。
  • 8645:由于没有可用于内存授予(排序和哈希)的内存,查询无法运行。
  • 8902:在 DBCC 执行期间未能分配内存。
  • 9695 或 9696:无法为 Service Broker 操作分配内存。
  • 17131 或 17132:由于内存不足,服务器启动失败。
  • 17890:由于 SQL 内存被操作系统换出,导致无法分配内存。
  • 18053:错误以 terse 模式打印,因为在格式设置过程中出错。 跳过跟踪、ETW 和通知。
  • 22986 或 22987:由于内存不足导致变更数据捕获失败。
  • 25601:Xevent 引擎内存不足。
  • 26053:由于内存不足,SQL 网络接口无法初始化。
  • 30085、30086、30094:SQL 全文操作由于内存不足而失败。

内存不足问题的原因

许多因素可能会导致内存不足。 这些因素包括操作系统设置、物理内存可用性、使用 SQL Server 中的内存的组件以及当前工作负荷的内存限制。 在大多数情况下,由于内存不足错误而失败的查询不是错误的原因。 可以将原因分为三个类别。

外部或 OS 内存压力

外部压力是指 SQL Server 进程外部某个组件的内存利用率过高,导致 SQL Server 可用内存不足。 检查系统上的其他应用程序是否消耗内存并导致内存不足。 SQL Server是少数旨在通过减少内存使用来响应 OS 内存压力的应用程序之一。 如果应用程序或驱动程序请求内存,OS 会向所有应用程序发送信号以释放内存,SQL Server通过减少自己的内存使用量做出响应。 很少有其他应用程序会做出响应,因为它们未设计为侦听该通知。 当SQL Server减少其内存使用量时,其内存池会收缩,并且需要内存的组件可能无法获取内存。 因此,你开始收到 701 或其他与内存相关的错误。 有关如何SQL Server动态分配和释放内存的详细信息,请参阅SQL Server内存体系结构。 有关详细的诊断和解决方案,请参阅本文中的 外部内存压力

三大类问题可能会导致 OS 内存压力:

  • 应用程序相关问题:一个或多个应用程序一起耗尽可用的物理内存。 OS 通过尝试释放某些内存来响应资源的新应用程序请求。 查找哪些应用程序耗尽内存,并采取措施在内存之间平衡内存,而无需耗尽 RAM。
  • 设备驱动程序问题:如果驱动程序错误地调用内存分配函数,设备驱动程序可能会导致所有进程的工作集分页。
  • 操作系统产品问题。

有关详细说明和故障排除步骤,请参阅 MSSQLSERVER_17890

来自非引擎模块的内部内存压力

内部内存压力是指由 SQL Server 进程内部的因素导致的低内存可用性。 在SQL Server进程中运行的某些组件位于SQL Server引擎的外部。 示例包括 OLE DB 提供程序(DLL),例如链接服务器、SQLCLR 过程或函数、扩展过程(XP)和 OLE 自动化(sp_OA*)。 其他程序包括将 DLL 注入进程以进行监视的防病毒或安全程序。 其中任何一个组件中出现的问题或设计不佳可能会导致内存消耗大。 例如,假设链接服务器将 2000 万行数据从外部源缓存到 SQL Server 内存中。 从引擎的角度来看,没有内存职员报告内存使用率高,但SQL Server进程中消耗的内存较高。 链接服务器 DLL 中的此内存增长导致SQL Server开始减少其内存使用率,并为引擎组件创建内存不足的情况,从而导致内存不足错误。 有关详细的诊断和解决方案,请参阅 来自非引擎模块的内部内存压力

注意

在SQL Server进程空间(例如 MSOLEDBSQLSQL Server Native Client)中使用的一些Microsoft DLL 可与用于报告和分配的SQL Server内存基础结构进行交互。 运行 SELECT * FROM sys.dm_os_memory_clerks WHERE type='MEMORYCLERK_HOST' 以获取它们的列表,并跟踪部分分配的内存消耗。 SQL Server Native Client (SNAC) 已弃用;新开发应使用 MSOLEDBSQL 或 Microsoft ODBC Driver for SQL Server。

来自SQL Server引擎组件的内部内存压力

来自 SQL Server 引擎内部组件的内存压力也可能导致内存不足错误。 通过内存职员跟踪的数百个组件在SQL Server中分配内存。 确定哪些内存分配器对最大的分配负责,以修复该问题。 例如,如果 OBJECTSTORE_LOCK_MANAGER 内存分配器显示分配量很大,请查明锁管理器为何消耗了这么多内存。 你可能会发现某些查询会获取大量锁。 通过使用索引、缩短长时间持锁事务的持续时间,或检查是否已禁用锁升级来优化这些查询。 每个内存职员或组件都有唯一的内存使用方式。 有关详细信息,请参阅 sys.dm_os_memory_clerks 和内存职员类型说明。 有关详细的诊断和解决方案,请参阅SQL Server引擎的内部内存使用情况

内存压力类型

下图演示了可能导致 SQL Server 内存不足的情况的压力类型:

此图显示了三种类型的SQL Server内存压力:外部/OS、非引擎模块内部以及引擎组件内部。

用于排查内存问题的诊断工具

使用以下诊断工具收集故障排除数据。

性能监视器

使用性能监视器设置和收集以下计数器:

  • 内存:可用 MB
  • 进程:工作集
  • 进程: 专用字节数
  • SQL Server:内存管理器:(所有计数器)
  • SQL Server:缓冲区管理器:(所有计数器)

DMV 和 DBCC MEMORYSTATUS

使用sys.dm_os_memory_clerksDBCC MEMORYSTATUS观察SQL Server内的总体内存使用率。 sys.dm_os_memory_clerks 为每个内存职员返回一行,并且是查找哪些组件消耗最多的内存的最佳起点。 DBCC MEMORYSTATUS 返回一个更详细的快照,该快照按内存管理器、缓冲池和职员对信息进行分组。

SSMS 中的内存消耗报表

若要查看SQL Server Management Studio中的内存使用情况(SSMS):

  1. 打开 SSMS 并连接到服务器。
  2. 对象资源管理器中,选择并按住SQL Server实例名称(或右键单击)。
  3. 在上下文菜单中,选择“ 报告>标准报表>内存消耗”。

PSSDiag 或 SQL LogScout

若要自动捕获这些数据点,请使用 PSSDiagSQL LogScout 等工具。

  • 如果使用 PSSDiag,请将其设置为捕获 Perfmon 收集器和 自定义诊断\SQL 内存错误 收集器。
  • 如果您使用 SQL LogScout,请将其设置为捕获 内存 场景。

快速缓解内存压力

以下操作可能会释放一些内存,并使其可用于SQL Server。 在调查根本原因时使用它们作为短期救济。

更改内存配置

检查以下 SQL Server 内存配置参数,并在可能的情况下考虑增加最大服务器内存

  • 最大服务器内存
  • 最小服务器内存

注意

如果发现异常设置,请根据需要更正这些设置,并考虑到内存需求增加的原因。 服务器内存配置选项中列出了默认设置。

如果未设置 最大服务器内存,尤其是启用了“内存中的锁定页”,请将其设置为特定值,以便为 OS 保留内存。 有关详细信息,请参阅 内存中的锁页 (LPIM) 服务器配置选项。

更改或移动工作负荷

查看查询工作负荷,包括并发会话数和当前正在运行的查询数。 检查是否可以暂时停止不太关键的应用程序,或者将其移动到另一个SQL Server实例。

对于只读工作负载,请考虑将其转移到 Always On 环境中的只读辅助副本上。 有关详细信息,请参阅 将只读工作负荷卸载到 AlwaysOn 可用性组 的次要副本,并 配置对 AlwaysOn 可用性组次要副本的只读访问权限。

检查虚拟机内存配置

如果您在虚拟机(VM)上运行 SQL Server,请确保主机不会为 VM 过度分配内存。 有关 Azure VM 上的 SQL Server 内存大小调整的指导,请参阅Azure 虚拟机上的 SQL Server 内存最佳做法。 对于 VMware 托管的 VM,请参阅虚拟机监控程序供应商关于检测和避免内存过度使用的文档。

释放 SQL Server 中的内存

运行以下一个或多个 DBCC 命令以释放SQL Server内存缓存。 在生产系统上谨慎使用这些命令,因为它们清除了必须重新填充的缓存:

  • DBCC FREESYSTEMCACHE
  • DBCC FREESESSIONCACHE
  • DBCC FREEPROCCACHE

重启 SQL Server 服务

如果内存耗尽至关重要,并且SQL Server无法处理查询,则可以将服务重启为最后手段。 此操作会删除所有活动连接并清除缓存,因此仅在其他选项失败时使用它。

查看 Resource Governor 设置

如果使用Resource Governor,请检查资源池和工作负荷组设置,确保它们不会太严重地限制内存。 有关详细信息,请参阅 Resource Governor

添加更多 RAM

如果上述步骤后问题仍然存在,请进一步调查,并考虑增加物理或虚拟服务器上的服务器资源(RAM)。

诊断和修复内存压力

如果内存不足错误只是偶尔出现,或只持续很短时间,那么该问题可能只是暂时性的,并且会自行解决,因此可能无需采取任何措施。 如果错误在多个连接中多次发生,并持续数秒或更长时间,请按照以下部分中的诊断和解决方案来确定并解决根本原因。

外部内存压力

若要诊断 SQL Server 进程外部系统上内存不足的情况,请使用以下方法:

  • 收集性能监视器计数器。 查看以下计数器,检查除SQL Server以外的应用程序或服务是否使用此服务器上的内存:

    • 内存:可用 MB
    • 进程:工作集
    • 进程: 专用字节数

    下面是使用 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)) }
    }
    }
    
  • 查看系统事件日志并查找与内存相关的错误(例如,虚拟内存不足)。

  • 查看应用程序事件日志,了解应用程序相关的内存问题。

    下面是查询关键字“memory”的系统和应用程序事件日志的示例 PowerShell 脚本。可以使用其他字符串(如“resource”)进行搜索:

    Get-EventLog System -ComputerName "$env:COMPUTERNAME" -Message "*memory*"
    Get-EventLog Application -ComputerName "$env:COMPUTERNAME" -Message "*memory*"
    
  • 修复不太关键的应用程序或服务中的代码或配置问题,以降低其内存使用量。

  • 如果除SQL Server以外的应用程序消耗资源,请尝试停止或重新安排它们,或在单独的服务器上运行它们。 这些步骤可消除外部内存压力。

来自非引擎模块的内部内存压力

若要诊断 SQL Server 内部模块(DLL)引起的内部内存压力,请使用以下方法:

  • 如果SQL Server不使用内存中的锁页(AWE API),则其大部分内存使用量显示在 性能监视器 中的 Process:Private Bytes 计数器(sqlservr实例)。 SQL Server:内存管理器:服务器内存总量(KB)计数器显示SQL Server引擎内的总体内存使用率。 如果在 Process:Private BytesSQL 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:内存管理器:服务器内存总量(KB)计数器显示SQL Server引擎内的总体内存使用率。 典型 Process:Private Bytes 值的总大小可能介于 300 MB 和 1-2 GB 之间。 如果 Process:Private Bytes 明显高于此典型范围,则差异可能来自 DLL(链接服务器、XP、SQLCLR 等)。 例如,如果专用字节为 4-5 GB,并且SQL Server使用内存中的锁页(AWE),则大部分专用字节可能来自SQL Server引擎外部。 此值是近似值。

  • 使用该tasklist实用工具标识在SQL Server进程中加载的 DLL。

    tasklist /M /FI "IMAGENAME eq sqlservr.exe"
    
  • 还可以使用以下查询来检查加载的模块(DLL),并检查是否存在任何意外内容:

    SELECT * FROM sys.dm_os_loaded_modules
    
  • 如果怀疑链接服务器模块导致显著的内存消耗,请通过清除 Allow inprocess 选项的勾选,将其配置为在进程外运行。 有关详细信息,请参阅 “创建链接服务器”。 并非所有链接服务器 OLE DB 提供程序都能在进程外运行。 有关详细信息,请联系提供商制造商。

  • 在极少数情况下,如果使用 OLE 自动化对象(sp_OA*),则可以通过将上下文值指定为 4(仅限本地 (.exe) OLE 服务器),把该对象配置为在 SQL Server 外部的进程中运行。 有关详细信息,请参阅 sp_OACreate

SQL Server引擎的内部内存使用情况

若要诊断SQL Server引擎内部组件的内部内存压力,请使用以下方法:

  • 开始收集 SQL Server 的性能监视器计数器:SQL Server:Buffer ManagerSQL Server:Memory Manager

  • 多次查询 SQL Server 的内存 clerk 动态管理视图 (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
    
  • 如果在内存职员中识别出明确的罪犯,请专注于该组件的内存消耗细节。 下面是几个示例:

    • 如果 MEMORYCLERK_SQLQERESERVATIONS 内存 clerk 正在占用内存,请识别使用大量内存授予的查询,并通过使用索引、重写这些查询(例如,删除 ORDER BY)或应用内存授予查询提示(MIN_GRANT_PERCENTMAX_GRANT_PERCENT)来优化这些查询。 有关详细信息,请参阅 查询提示。 还可以 创建资源池 来控制内存授予使用情况。 有关内存授予的详细信息,请参阅排查SQL Server中内存授予导致的性能缓慢或内存不足问题
    • 如果缓存了许多即席查询计划,则 CACHESTORE_SQLCP 内存职员将使用大量的内存。 识别其执行计划无法重用的非参数化查询,并通过将其转换为存储过程、使用 sp_executesql 或使用 FORCED 参数化来实现参数化。 如果启用了 跟踪标志 174 ,则可以禁用它以查看该问题是否已修复。
    • 如果对象计划缓存存储 CACHESTORE_OBJCP 占用过多的内存,请确定哪些存储过程、函数或触发器使用大量内存,并考虑重新设计应用程序。 这通常发生在包含数百个过程的许多数据库或架构中。
    • 如果 OBJECTSTORE_LOCK_MANAGER 内存文员显示大量分配,请识别获取大量锁的查询,并使用索引对其进行优化。 缩短在某些隔离级别下长时间持有锁的事务,或检查是否已禁用锁升级。
    • 如果观察到非常大 TokenAndPermUserStoreSELECT type, name, pages_kb FROM sys.dm_os_memory_clerks WHERE name = 'TokenAndPermUserStore'),可以使用 跟踪标志 4618 来限制缓存大小。
    • 如果发现来自 MEMORYCLERK_XTP memory clerk 的与 In-Memory OLTP 相关的内存问题,请参阅 监视和排查 In-Memory OLTP 的内存使用情况内存优化的 tempdb 元数据 (HkTempDB) 内存不足错误

常见问题解答

为什么SQL Server使用服务器上的几乎所有 RAM?

根据设计,SQL Server缓冲池增长到缓存数据页并减少物理 I/O,因此稳定状态内存的使用通常接近最大服务器内存设置。 此行为是预期的,不是泄漏。 若要限制消耗量并为 OS 和其他进程留出空间,请配置 最大服务器内存。 有关详细信息,请参阅 服务器内存配置选项

最大服务器内存与任务管理器中显示的已提交内存有什么区别?

最大服务器内存设置会限制SQL Server缓冲池和引擎内大多数内存职员可以提交的内存。 任务管理器显示整个 sqlservr.exe 进程的已提交内存。 此视图包括组件在缓冲池外部做出的分配,例如 CLR、链接服务器提供程序、扩展存储过程和备份缓冲区。 因此,总进程内存可能超过 最大服务器内存。 有关详细信息,请参阅 内存管理体系结构指南

何时应在内存中启用锁定页(LPIM)?

当 OS 剪裁SQL Server的工作集时,在内存中启用锁定页。 此问题表现为错误 17890,或服务器总内存突然下降。 将 LPIM 与显式 最大服务器内存 值配对,以便为 OS 和其他进程保留 RAM。 不要在每个实例上默认启用 LPIM。 用它来处理已确认的分页问题。

sys.dm_os_memory_clerks告诉我什么?

sys.dm_os_memory_clerks为 SQL Server 引擎内每个活动的内存分配对象返回一行,其中包含该对象已提交的内存量。 使用它来查找哪个组件(例如缓冲池、计划缓存、锁定管理器或查询内存授予)消耗了最多的内存并指导优化工作。