内存管理体系结构指南

适用于:SQL ServerAzure SQL 数据库Azure SQL 托管实例Azure Synapse AnalyticsAnalytics Platform System (PDW)

Windows 虚拟内存管理器

提交的地址空间区域由 Windows 虚拟内存管理器 (VMM) 映射到可用的物理内存。

有关不同操作系统所支持的物理内存量的详细信息,请参阅介绍 Windows 版本的内存限制的 Windows 文档。

虚拟内存系统允许虚拟内存超过物理内存,这样虚拟内存与物理内存的比率可以大于 1:1。 因此,大型程序在计算机上运行时可以具有多种物理内存配置。 但是,使用比所有进程的平均组合工作集大得多的虚拟内存可能会导致性能降低。

SQL Server 内存体系结构

SQL Server 将根据需要动态获取并释放内存。 虽然该选项仍然存在且在有些环境下需要用到,但通常情况下管理员不必指定应为 SQL Server 分配多少内存。

所有数据库软件的主要设计目标之一是尽量减少磁盘 I/O,因为磁盘的读取和写入操作占用大量资源。 SQL Server 在内存中生成缓冲池,用于保存从数据库读取的页。 SQL Server 中的大量代码专门用于尽量减少磁盘与缓冲池之间的物理读写次数。 SQL Server 设法在以下两个目标之间达到平衡:

  • 防止缓冲池变得过大,从而导致整个系统内存不足。
  • 尽量增加缓冲池达的大小,以便尽量减少数据库文件的物理 I/O。

在负载过重的系统中,某些在运行时需要大量内存的大型查询不能获取所需的最小内存量,并在等待内存资源时收到超时错误。 若要解决此问题,请增大 query wait 选项。 对于并行查询,请考虑减小 最大并行度选项

在负载过重而内存不足的系统中,对于查询计划中带有合并联接、排序和位图的查询,如果无法获得位图所需的最小内存量,可以删除位图。 这会影响查询性能,并且如果排序过程无法容纳在内存中,就会增加 tempdb 数据库中工作表的使用量,从而导致 tempdb 增大。 要解决此问题,可添加物理内存或优化查询以使用其他更快速的查询计划。

常规(虚拟)内存

所有 SQL Server 版本都支持 64 位平台上的常规内存。 对于 x64 体系结构,SQL Server 进程可以访问不超过操作系统最大容量的虚拟地址空间(SQL Server Standard Edition 最高可支持 128GB)。 对于 IA64 体系结构时,上限为 7TB,但 SQL Server 2012 (11.x) 及更高版本不支持 IA64。 有关详细信息,请参阅 Windows 内存限制

地址窗口扩展 (AWE) 内存

通过使用 地址窗口扩展 (AWE) 和 AWE 需要的 内存中锁页 (LPIM) 特权,可以在虚拟内存不足条件下将大多数 SQL Server 进程内存“锁定”在物理 RAM 中。 32 位和 64 位 AWE 分配都可实现这一目的。 由于 AWE 内存不会通过 Windows 中的虚拟内存管理器(用于控制内存分页),因此会锁定内存。 AWE 内存分配 API 需要“在内存中锁定页”(SeLockMemoryPrivilege) 特权;请参阅 AllocateUserPhysicalPages 说明。 因此,使用 AWE API 的主要优点是,可系统存在内存压力时确保大部分内存驻留在 RAM 中。 有关如何允许 SQL Server 使用 AWE 的信息,请参阅 启用“在内存中锁定页”选项

如果授予了 LPIM 特权,则强烈建议将“最大服务器内存 (MB)”设置为某个具体的值,而不是保留 2,147,483,647 兆字节 (MB) 的默认值。 有关详细信息,请参阅 服务器内存服务器配置:手动设置选项服务器在内存中锁定页 (LPIM)

如果未启用 LPIM,SQL Server 将切换使用常规内存,操作系统内存耗尽时可能会在错误日志中报告错误 17890。 下面的示例重现了这一错误:

A significant part of SQL Server process memory has been paged out. This may result in a performance degradation. Duration: #### seconds. Working set (KB): ####, committed (KB): ####, memory utilization: ##%.

从 SQL Server 2012 (11.x) 开始有关内存管理的更改

早期版本的 SQL Server 使用五种不同的机制分配内存:

  • 单页分配器 (SPA),其中仅包含 SQL Server 进程中小于等于 8KB 的内存分配。 “max server memory (MB)”和“min server memory (MB)”这两个配置选项确定 SPA 消耗的物理内存的限制。 同时,缓冲池也是用于 SPA 的机制,并且还是最大的单页分配使用者。
  • 多页分配器 (MPA),用于需要 8 KB 以上的内存分配。
  • CLR 分配器,包含 CLR 初始化期间创建的 SQL CLR 堆及其全局分配。
  • 为 SQL Server 进程中的 线程堆栈 分配的内存。
  • 直接 Windows 分配 (DWA),用于直接向 windows 进行的内存分配请求。 这包括加载到 SQL Server 进程中的模块使用的 Windows 堆和直接分配的虚拟内存。 此类内存分配请求的示例包括从扩展存储过程 DLL 分配、使用自动化过程(sp_OA 调用)创建的对象以及从链接服务器提供程序分配。

从 SQL Server 2012 (11.x) 开始,单页分配、多页分配和 CLR 分配模式全部合并到“任意大小”页分配器中,受到由“最大服务器内存 (MB)”和“最小服务器内存 (MB)”配置选项控制的内存限制。 此更改使通过 SQL Server 内存管理器的所有内存要求能更准确地调整大小。

重要

升级到 SQL Server 2012 (11.x) 及更高版本后,请仔细检查当前的“最大服务器内存 (MB)”和“最小服务器内存 (MB)”配置。 这是因为从 SQL Server 2012 (11.x) 开始,与早期版本相比,这些配置现在包括并用于更多内存分配。 这些更改适用于 32 位和 64 位版本的 SQL Server 2012 (11.x) 和 SQL Server 2014 (12.x) 以及 64 位版本的 SQL Server 2016 (13.x) 及更高版本。

下表指示特定类型的内存分配是否受“max server memory (MB)和“min server memory (MB)”配置选项控制

内存分配的类型 SQL Server 2005 (9.x)、SQL Server 2008 (10.0.x) 和 SQL Server 2008 R2 (10.50.x) 从 SQL Server 2012 (11.x) 起
单页分配 是,合并到“任意大小”页分配
多页分配 是,合并到“任意大小”页分配
CLR 分配
线程堆栈内存
从 Windows 直接分配

从 SQL Server 2012 (11.x) 开始,SQL Server 可能会分配比“最大服务器内存 (MB)”设置中指定的值更多的内存。 当“总服务器内存 (KB)”值已达到“目标服务器内存 (KB)”设置(由“最大服务器内存 (MB)”指定)时,则可能会出现这种情况。 如果因内存碎片造成连续空闲内存不足,无法满足多页内存请求的需求(超过 8KB),SQL Server 可以执行超额承诺使用量,而不是拒绝内存请求。

执行此分配后,资源监视器后台任务会立即开始向所有内存消耗者发送信号,指示其释放已分配的内存,并尝试使“总服务器内存 (KB)”值低于“目标服务器内存 (KB)”设置。 因此,SQL Server 内存使用情况可短暂超过“最大服务器内存 (MB)”设置。 在这种情况下,“总服务器内存 (KB)”性能计数器读数将超过“最大服务器内存 (MB)”和“目标服务器内存 (KB)”设置。

在以下操作中通常会观察到此行为:

  • 大型列存储索引查询
  • 大型行存储上的批处理模式查询
  • 会使用大量内存来执行哈希和排序操作的列存储索引(重新)生成
  • 需要较大内存缓冲区的备份操作
  • 需要存储较大输入参数的跟踪操作

从 SQL Server 2012 (11.x) 开始对 memory_to_reserve 的更改

在早期版本的 SQL Server 中,SQL Server 内存管理器预留了一部分进程虚拟地址空间 (VAS),以供多页分配器 (MPA)、CLR 分配器、用于 SQL Server 进程中的线程堆栈的内存分配以及直接 Windows 分配 (DWA) 使用。 这一部分虚拟地址空间也称为“Mem-To-Leave”或“非缓冲池”区域。

为这些分配保留的虚拟地址空间是由 memory_to_reserve 配置选项确定的。 SQL Server 使用的默认值是 256 MB。

由于“任何大小”页分配器也处理大于8 KB 的分配,因此 memory_to_reserve 值不包括多页分配。 除了此更改以外,其他设置都与此配置选项相同。

下表指示特定类型的内存分配是否属于 SQL Server 进程虚拟地址空间的 memory_to_reserve 区域:

内存分配的类型 SQL Server 2005 (9.x)、SQL Server 2008 (10.0.x) 和 SQL Server 2008 R2 (10.50.x) 从 SQL Server 2012 (11.x) 起
单页分配 否,合并到“任意大小”页分配
多页分配 否,合并到“任意大小”页分配
CLR 分配
线程堆栈内存
从 Windows 直接分配

动态内存管理

SQL Server 数据库引擎的默认内存管理行为是在不造成系统内存不足的情况下获取尽可能多的内存。 SQL Server 数据库引擎通过使用 Microsoft Windows 中的内存通知 API 来实现这一点。

SQL Server 动态使用内存时,将会定期查询系统以确定可用内存大小。 保持此可用内存可避免操作系统 (OS) 进行分页。 如果可用内存较少,SQL Server 将会释放内存以供操作系统使用。 如果有更多的内存可用,SQL Server 可能会分配更多的内存。 SQL Server 仅在其工作负荷需要更多内存时才增加内存;服务器空闲时不会增加其虚拟地址空间的大小。 如果发现当 SQL Server 使用动态内存管理时,任务管理器和性能监视器显示可用内存在稳步减少,这属于默认行为,而不应被视为内存泄漏。

“最大服务器内存”控制 SQL Server 内存分配、编译内存、所有缓存(包括缓冲池)、查询执行内存授予锁管理器内存和 CLR1 内存(实质上是 sys.dm_os_memory_clerks 中存在的任何内存分配器)。

1 从 SQL Server 2012 (11.x) 开始,CLR 内存在 max_server_memory 分配下管理。

以下查询返回有关当前分配内存的信息:

SELECT
  physical_memory_in_use_kb/1024 AS sql_physical_memory_in_use_MB,
    large_page_allocations_kb/1024 AS sql_large_page_allocations_MB,
    locked_page_allocations_kb/1024 AS sql_locked_page_allocations_MB,
    virtual_address_space_reserved_kb/1024 AS sql_VAS_reserved_MB,
    virtual_address_space_committed_kb/1024 AS sql_VAS_committed_MB,
    virtual_address_space_available_kb/1024 AS sql_VAS_available_MB,
    page_fault_count AS sql_page_fault_count,
    memory_utilization_percentage AS sql_memory_utilization_percentage,
    process_physical_memory_low AS sql_process_physical_memory_low,
    process_virtual_memory_low AS sql_process_virtual_memory_low
FROM sys.dm_os_process_memory;

堆栈大小

用于线程堆栈1、CLR2、扩展过程 .dll 文件、分布式查询引用的 OLE DB 提供程序以及 Transact-SQL 语句中引用的自动化对象的内存,以及由非 DLL 分配的任何内存均不受“最大服务器内存 (MB)”控制。

1 有关为当前主机中给定数量的关联 CPU 计算得出的默认工作线程数信息,请参阅介绍如何 配置“最大工作线程数”服务器配置选项 的文章。 SQL Server 堆栈大小如下所示:

SQL Server 体系结构 操作系统体系结构 堆栈大小
x86(32 位) x86(32 位) 512 KB
x86(32 位) x64 (64 位) 768 KB
x64 (64 位) x64 (64 位) 2048 KB
IA64 (Itanium) IA64 (Itanium) 4096 KB

2 从 SQL Server 2012 (11.x) 开始,CLR 内存在 max_server_memory 分配下管理。

SQL Server 使用内存通知 API QueryMemoryResourceNotification 来确定 SQL Server 内存管理器何时可以分配内存和释放内存。

SQL Server 启动时,将根据多个参数(例如系统的物理内存大小、服务器线程数和各个启动参数)计算缓冲池的虚拟地址空间大小。 SQL Server 将为缓冲池保留计算得到的进程虚拟地址空间量,但它仅为当前负荷获取(提交)所需的物理内存量。

然后实例将继续获取支持工作负荷所需的内存。 随着用户连接和运行查询的逐步增多,SQL Server 将按需获取更多的物理内存。 SQL Server 实例将继续获取物理内存,直到达到自己的“最大服务器内存 (MB)”分配目标或操作系统指示不再有可用剩余内存;如果该实例获取的内存超过“最小服务器内存”设置,并且操作系统指示可用内存短缺,将释放内存。

随着在运行 SQL Server 实例的计算机上启动其他应用程序,这些应用程序将会占用内存,从而使可用物理内存量降到 SQL Server 目标以下。 SQL Server 实例将调整其内存使用量。 如果另一个应用程序已停止,并且可用内存增多,SQL Server 的实例会增加其内存分配的大小。 SQL Server 每秒可以释放和获取几 MB 的内存,从而根据内存分配变化快速做出调整。

min server memory 和 max server memory 的影响

“最小服务器内存”和“最大服务器内存”配置选项确定了缓冲池和数据库引擎的其他缓存所用内存大小的上限和下限。 缓冲池不会立即获取“最小服务器内存”中指定的内存量。 缓冲池启动时只使用初始化所需的内存。 随着 SQL Server 数据库引擎工作负荷的增加,将会继续获取支持工作负荷所需的内存。 在达到“最小服务器内存”指定的内存大小之前,缓冲池不会释放其获取的任何内存。 达到最小服务器内存后,缓冲池将使用标准算法,根据需要来获取和释放内存。 唯一的区别是缓冲池从不将内存分配降到“最小服务器内存”设置水平下,也从不获取超过“最大服务器内存 (MB)”设置水平的内存。

注意

SQL Server 进程将获取超过“最大服务器内存 (MB)”选项指定值的内存。 内部和外部组件都可以分配缓冲池以外的内存,这将占用额外的内存,但是分配给缓冲池的内存通常仍在 SQL Server 占用的内存中占最大份额。

SQL Server 数据库引擎获取的内存大小完全取决于放置在实例上的工作负荷。 不处理很多请求的 SQL Server 实例可能永远不会达到“最小服务器内存”。

如果为“最小服务器内存”和“最大服务器内存 (MB)”指定的值相同,则一旦分配给 SQL Server 数据库引擎的内存达到该值,SQL Server 数据库引擎将停止为缓冲池动态释放和获取内存。

如果在运行 SQL Server 实例的计算机上频繁启动或停止其他应用程序,启动这些应用程序所需的时间可能会因 SQL Server 实例分配和释放内存而延长。 另外,如果 SQL Server 是几个在一台计算机上运行的服务器应用程序中的一个,系统管理员可能需要控制分配给 SQL Server 的内存量。 在这些情况下,可以使用“最小服务器内存”和“最大服务器内存 (MB)”选项控制 SQL Server 可以使用的内存大小。 “min server memory”和“max server memory”选项均以 MB 为单位指定。 有关详细信息,包括有关如何设置这些内存配置的建议,请参阅服务器内存配置选项

SQL Server 对象规范使用的内存

以下列表介绍 SQL Server 中不同对象所用内存量的近似值。 列出的数值为估计值,根据环境以及创建对象的方式可能有所不同:

  • 锁(由锁管理器维护):64 字节 + 每个所有者 32 字节
  • 用户连接:约为 (3 * network_packet_size + 94 KB)

“网络数据包大小”是用于在应用程序和数据库引擎之间通信的表格格式数据流 (TDS) 数据包大小。 默认的数据包大小为 4 KB,由“网络数据包大小”配置选项控制。

启用多重活动结果集 (MARS) 时,用户连接约为 (3 + 3 * num_logical_connections) * network_packet_size + 94 KB。

min memory per query 的影响

min memory per query 配置选项设定将为执行查询分配的最小内存量 (KB)。 这也称为最小内存授予。 所有查询必须等待,直到:可确保提供请求的最小内存,可以启动执行,或者已超过查询等待服务器配置选项中指定的值。 此场景中累积的等待类型是 RESOURCE_SEMAPHORE

重要

不要将“最小每次查询内存”服务器配置选项设置过高,尤其是在非常繁忙的系统上,因为这样做可能导致下列问题:

  • 内存资源竞争加剧。
  • 即使所需的内存在运行时低于此配置,也会通过增加每次单个查询的内存量来降低并发。

有关使用此配置的建议,请参阅配置 min memory per query 服务器配置选项

内存授予注意事项

对于“行模式执行”,任何情况下都不得超过初始内存授予。 如果执行哈希或排序操作需要的内存多于初始授予,这些内存将溢出到磁盘。 溢出的哈希操作由 tempdb 中的 Workfile 支持,而溢出的排序操作由工作表支持。

排序操作期间发生的溢出称为排序警告。 排序警告指示排序操作的内存不足。 这不包括涉及创建索引的排序操作,只包括查询内的排序操作(例如 SELECT 语句中使用的 ORDER BY 子句)。

哈希操作期间发生的溢出称为哈希警告。 在哈希操作过程中发生哈希递归或哈希终止(哈希释放)时,会出现此情况。

  • 生成输入无法装入可用内存时,会发生哈希递归,这将导致输入分割成单独处理的多个分区。 如果这些分区中的任何一个仍然大于可用内存,则该分区将继续拆分成子分区,并分别进行处理。 此拆分过程将一直持续到每个分区都小于可用内存,或达到最大递归级数。
  • 当哈希操作达到其最大递归级数并转换到替换计划以处理剩余的分区数据时发生哈希释放。 这些事件可导致服务器性能降低。

对于“批模式执行”,默认情况下,初始内存授予可以动态增加到特定内部阈值。 此动态内存授予机制旨在允许在批模式下运行哈希或排序操作的内存驻留执行。 如果这些操作仍然内存不足,则将溢出到磁盘。

有关执行模式的详细信息,请参阅查询处理体系结构指南

缓冲区管理

SQL Server 数据库的主要用途是存储和检索数据,因此,大量磁盘 I/O 是该数据库引擎的一个核心特点。 此外,由于磁盘 I/O 操作要消耗许多资源,并且需要较长时间才能完成,所以 SQL Server 侧重于提高 I/O 效率。 缓冲区管理是实现高效 I/O 操作的关键环节。 缓冲区管理组件由下列两种机制组成:用于访问及更新数据库页的缓冲区管理器和用于减少数据库文件 I/O 的缓冲区高速缓存(又称为“缓冲池”)

缓冲区管理的工作原理

一个缓冲区就是一个 8KB 大小的内存页,其大小与一个数据页或索引页相当。 因此,缓冲区缓存被划分为多个 8-KB 页。 缓冲区管理器负责将数据页或索引页从数据库磁盘文件读入缓冲区高速缓存中,并将修改后的页写回磁盘。 缓冲区缓存中会保留一页,直到缓冲区管理器需要该缓冲区读入更多数据。 数据只有在被修改后才重新写入磁盘。 在将缓冲区缓存中的数据写回磁盘之前,可对其进行多次修改。 有关详细信息,请参阅 读取页写入页

SQL Server 启动时,将根据多个参数(例如系统的物理内存大小、配置的最大服务器线程数和各种启动参数)计算缓冲池的虚拟地址空间大小。 SQL Server 将为缓冲区缓存保留此计算得到的进程虚拟地址空间量(称为内存目标),但它仅为当前负荷获取(提交)所需的物理内存量。 可查询 sys.dm_os_sys_info 目录视图中的 committed_target_kb 和 committed_kb 列,分别返回保留为内存目标的页数以及缓冲区缓存中当前提交的页数

SQL Server 启动与缓冲区缓存获得其内存目标之间的间隔称为“增长期”。 在此期间,读取请求将根据需要填充缓冲区。 例如,一个 8 KB 的单页读取请求会填充一个缓冲区页。 也就是说,增长期取决于客户端请求的数量和类型。 通过将单页读取请求转换为对齐的八页请求加快增长(组成一个盘区)。 这样可更快地完成增长,尤其是那些内存容量很大的机器。 有关页和盘区的详细信息,请参阅 页和盘区体系结构指南

因为缓冲区管理器将大多数内存用于 SQL Server 进程,所以它会与内存管理器协作以使其他组件能使用其缓冲区。 缓冲区管理器主要与下列组件交互:

  • 资源管理器。此交互用于控制内存的整体使用情况以及 32 位平台中的地址空间使用情况。
  • 数据库管理器和 SQL Server 操作系统 (SQLOS)。此交互用于低级文件 I/O 操作。
  • 日志管理器。此交互用于预写日志记录。

支持的功能

缓冲区管理器支持以下功能:

  • 缓冲区管理器可识别非一致性内存访问 (NUMA)。 缓冲区高速缓存页将跨硬件 NUMA 节点进行分布,它允许线程访问分配到本地 NUMA 节点上的缓冲区页,而不是从外部内存访问。

  • 缓冲区管理器支持热添加内存,用户无需重新启动服务器即可添加物理内存

  • 缓冲区管理器在 64 位平台上支持大型页。 页大小因 Windows 版本不同而异。

    注意

    在 SQL Server 2012 (11.x) 之前,在 SQL Server 中启用大型页需要跟踪标志 834

  • 缓冲区管理器提供通过动态管理视图显示的额外诊断信息。 可以使用这些视图来监视特定于 SQL Server 的各种操作系统资源。 例如,可以使用 sys.dm_os_buffer_descriptors 视图来监视缓冲区缓存中的页。

磁盘 I/O

缓冲区管理器仅对数据库执行读写操作。 其他文件和数据库操作(如打开、关闭、扩展和收缩)则由数据库管理器和文件管理器组件执行。

缓冲区管理器的磁盘 I/O 操作具有以下特点:

  • 所有 I/O 操作均异步执行。这样,在后台进行 I/O 操作的同时,即可调用线程继续处理。
  • 所有 I/O 操作均在调用线程中发出,除非 affinity I/O 选项处于使用状态。 关联 I/O 掩码 选项将 SQL Server 磁盘 I/O 绑定到指定的 CPU 子集。 在高端 SQL Server 联机事务处理 (OLTP) 环境中,此扩展可以提高 SQL Server 线程执行 I/O 的性能。
  • 可通过散播-聚集 I/O 实现多页 I/O,散播-聚集 I/O 允许数据传入或传出非连续内存区域。 这意味着 SQL Server 可以快速填充或刷新缓冲区缓存,同时避免多个物理 I/O 请求。

长时 I/O 请求

缓冲区管理器报告任何经过 15 秒或更长时间仍未完成的 I/O 请求。 这可以帮助系统管理员区分 SQL Server 问题和 I/O 子系统问题。 将报告错误消息 833 并且该消息在 SQL Server 错误日志中显示如下:

SQL Server has encountered ## occurrence(s) of I/O requests taking longer than 15 seconds to complete on file [##] in database [##] (#). The OS file handle is 0x00000. The offset of the latest long I/O is: 0x00000.

长时 I/O 可以是读或写,不过当前消息并未指明。 长时 I/O 消息是警告而不是错误。 它们并不表示 SQL Server 存在问题,而是基础 I/O 系统存在问题。 报告这些消息是为了帮助系统管理员更快地找到 SQL Server 响应缓慢的原因,并找出 SQL Server 无法控制的问题。 因此,不需要执行任何操作,但系统管理员应调查 I/O 请求耗时很长的原因以及耗时是否合理。

长时 I/O 请求的原因

长时 I/O 消息可能指示 I/O 永久阻塞并且永远无法完成(称为“I/O 丢失”),或者只是它尚未完成。 虽然 I/O 丢失往往会导致闩锁超时,但无法根据消息确定是哪种情况。

长时 I/O 往往指示磁盘子系统的 SQL Server 工作负荷过于密集。 以下情况可能会指示磁盘子系统不足:

  • SQL Server 工作负荷很大时,错误日志中出现多个长时 I/O 消息。
  • 性能监视器计数器将显示磁盘长时间滞后、磁盘队列长或无磁盘空闲时间。

长时 I/O 还可能因以下原因所致:I/O 路径中的某个组件(如驱动程序、控制器或固件)不断延迟为早期 I/O 请求提供服务,而为距离当前磁头位置较近的新请求提供服务。 优先处理最靠近当前读/写头位置的请求的常用技术称为“电梯查找”。这可能很难用性能监视器工具佐证,因为大多数 I/O 都会及时提供处理。 执行大量连续 I/O 的工作负荷可能会使长时 I/O 请求情况更严重,如备份和还原、表扫描、排序、创建索引、大容量加载以及清零文件。

单独出现的长时 I/O 如果与上述情况无关,则可能是由硬件或驱动程序问题所致。 系统事件日志可能会包含有助于进行问题诊断的相关事件。

内存压力检测

内存压力是由内存不足造成的一种情况,可导致:

  • 额外的 I/O(如非常活跃的 lazy writer 后台线程)
  • 重新编译比升高
  • 运行查询延长(如果存在内存授予等待)
  • 额外的 CPU 周期

这种情况可能由外部或内部原因引发。 外部原因包括:

  • 可用物理内存 (RAM) 不足。 这会导致系统调整当前正在运行的进程的工作集,并可能导致整体速度下降。 SQL Server 可能减少缓冲池的提交目标,并更频繁地开始调整内部缓存。
  • 总体可用系统内存(包括系统页面文件)不足。 这可能导致系统无法分配内存,因为无法对当前已分配的内存进行页面换出。

内部原因包括:

  • 在 SQL Server 数据库引擎置较低的内存使用情况上限时响应外部内存压力。
  • 通过减少 max server memory 配置手动降低内存设置
  • 多个缓存之间的内部组件的内存分配发生变化。

SQL Server 数据库引擎实现了一个专门用于检测和处理内存压力的框架,包含在动态内存管理中。 此框架包含名为“资源监视器”的后台任务。 “资源监视器”任务可监视外部和内部内存指示器的状态。 一旦某个指示器改变状态,它会计算相应的通知并进行广播。 这些通知是每个引擎组件中的内部消息,存储在环形缓冲区中。

两个环形缓冲区保留与动态内存管理相关的信息:

  • “资源监视器”环形缓冲区,用于跟踪“资源监视器”活动,例如是否有内存压力信号。 此环形缓冲区的状态信息取决于 RESOURCE_MEMPHYSICAL_HIGHRESOURCE_MEMPHYSICAL_LOWRESOURCE_MEMPHYSICAL_STEADYRESOURCE_MEMVIRTUAL_LOW 的当前条件。
  • “内存代理”环形缓冲区,其中包含每个 Resource Governor 资源池的内存通知记录。 检测到内部内存压力时,将为分配内存的组件启用内存不足通知,以触发用于平衡缓存之间的内存的操作。

内存代理监视每个组件对内存的需求消耗,然后根据收集的信息计算所有这些组件的最佳内存值。 每个 Resource Governor 资源池都有一组代理。 它们随后可将此信息广播给每个组件,使组件根据需要增加或减少用量。

有关内存代理的详细信息,请参阅 sys.dm_os_memory_brokers

错误检测

数据库页可使用下面两种可选机制中的一种,来确保页在从写入磁盘到再次读取期间的完整性:残缺页保护和校验和保护。 这些机制允许采用独立方法验证数据存储以及诸如控制器、驱动程序、电缆等硬件组件甚至操作系统的正确性。 在即将把页写入磁盘之前将向页添加保护,并在从磁盘读取页后对它进行验证。

SQL Server 将对因校验和、页撕裂或其他 I/O 错误而失败的任何读取都重试四次。 如果在其中一次重试中读取成功,则会向错误日志中写入一条消息,且触发读取的命令将继续执行。 如果重试失败,则该命令失败,且显示错误消息 824。

页保护类型是包含此页的数据库的属性之一。 校验和保护是 SQL Server 2005 (9.x) 和更高版本中所创建数据库的默认保护。 页保护机制是在创建数据库时指定的,并且可以使用 ALTER DATABASE SET 进行更改。 可通过查询 sys.databases 目录视图中的 page_verify_option 列或 DATABASEPROPERTYEX 函数的 IsTornPageDetectionEnabled 属性来确定当前的页保护设置。

注意

如果页保护设置发生变化,新设置不会立即影响到整个数据库。 相反,每当下一次写入时,这些页才会采用数据库的当前保护级别。 这意味着数据库可能包含带有不同保护的页。

残缺页保护

SQL Server 2000 (8.x) 中引入的残缺页保护功能主要是一种对电源故障导致的页损坏进行检测的方法。 例如,意外电源故障可能导致只有部分页写入磁盘。 使用残缺页保护时,在将 8 KB 的数据库页写入磁盘时,该页的每个 512 字节的扇区都有一个特定的 2 位签名模式存储在数据库的页头中。

从磁盘中读取页时,页头中存储的残缺位将与实际的页扇区信息进行比较。 每次进行写操作时,这个签名模式在二进制数 0110 之间交替,这样始终可以确定是否只有部分扇区写到磁盘:如果稍后读取页时发现某个位的状态不正确,则说明该页没有被正确写入并会因此检测到残缺页。 残缺页检测使用的资源极少,但无法检测到磁盘硬件故障导致的所有错误。 有关设置残缺页保护的信息,请参阅 ALTER DATABASE SET 选项 (Transact-SQL)

校验和保护

SQL Server 2005 (9.x) 中引入的校验和保护功能提供了更强大的数据完整性检查。 此方法将对写入每一页中的数据进行校验和计算并将其值存储在页头中。 每次从磁盘读取存储了校验和的页时,数据库引擎将重新计算页中数据的校验和。如果新的校验和不同于已存储的校验和,则引发错误 824。 校验和保护比残缺页保护能捕获到更多的错误,因为它受到页中每个字节的影响,但它对资源的消耗较多。

启用校验和后,当缓冲区管理器从磁盘读取页时均可以检测到因电源故障以及硬件或固件故障导致的错误。 有关设置校验和的信息,请参阅 ALTER DATABASE SET 选项 (Transact-SQL)

重要

将用户或系统数据库升级到 SQL Server 2005 (9.x) 或更高版本时,将保留 PAGE_VERIFY 的值(NONETORN_PAGE_DETECTION)。 强烈建议使用 CHECKSUMTORN_PAGE_DETECTION 使用的资源可能较少,但提供的 CHECKSUM 保护最少。

了解非一致性内存访问

SQL Server 能识别非一致性内存访问 (NUMA),无需特殊配置便可在 NUMA 硬件上顺利地执行。 随着处理器时钟速度的提高和处理器数量的增加,使用这种额外处理能力所需的内存滞后时间越来越难以减少。 为了避开这一问题,硬件供应商提供了大型的 L3 缓存,但这只是一种有限的解决方案。 NUMA 体系结构为此问题提供了可缩放的解决方案。

SQL Server 已设计旨在利用基于 NUMA 的计算机而无需更改任何应用程序。 有关详细信息,请参阅 如何:将 SQL Server 配置为使用软件 NUMA

内存对象的动态分区

SQL Server 中的堆分配器称为内存对象,数据库引擎可通过这些对象从堆中分配内存。 可以使用 sys.dm_os_memory_objects DMV 跟踪它们。

CMemThread 是一个线程安全内存对象类型,它允许从多个线程并发分配内存。 为实现正确的跟踪,CMemThread 对象依赖于同步构造(互斥体),以确保同一时间只有一个线程更新重要信息。

注意

在整个数据库引擎代码库中,CMemThread 对象类型用于许多不同的分配,并可按节点或 CPU 进行全局分区。

但是,如果很多线程以高度并发的方式从同一内存对象进行分配,则使用互斥体可能会导致争用。 因此,SQL Server 具有分区内存对象 (PMO) 的概念,每个分区由单个 CMemThread 对象表示。 内存对象的分区是静态定义的,创建后不能更改。 由于内存分配模式因硬件和内存使用情况等方面迥然相异,因此不可能提前设定出完美的分区模式。

在大多数情况下,使用单个分区就足够了,但在某些情况下,这可能会导致争用,只能通过高度分区的内存对象阻止这种争用。 不宜对每个内存对象进行分区,因为分区增多可能导致其他方面的效率低下并增加内存碎片。

注意

在 SQL Server 2016 (13.x) 之前,可以使用跟踪标志 8048 将基于节点的 PMO 强制变为基于 CPU 的 PMO。 从 SQL Server 2014 (12.x) SP 2 和 SQL Server 2016 (13.x) 开始,此行为是动态的,由引擎控制。

从 SQL Server 2014 (12.x) SP2 和 SQL Server 2016 (13.x) 开始,数据库引擎可以动态检测特定 CMemThread 对象上的争用,并将对象提升为基于每个节点或每个 CPU 的实现。 提升后,PMO 会保持提升状态,直到重新启动 SQL Server 进程。 sys.dm_os_wait_stats DMV 中 CMEMTHREAD 等待数过多可指示 CMemThread 争用,可通过观察以下 sys.dm_os_memory_objects DMV 列来发现:contention_factorpartition_typeexclusive_allocations_countwaiting_tasks_count

后续步骤