SQL Server 的内存管理是一个庞大的主题,涉及特别多的概念和技术,例如常见的 Plan Cache、Buffer Pool、Memory Clerks 等。本文仅是管中窥豹,描述常见的内存管理相关概念。
在了解内存管理之前,通过 sys.dm_os_memory_clerks 视图可以查询内存的使用职责(Memory Clerks),也就是内存的消耗者。
SELECT [type], SUM(pages_kb) AS total_pages_kb FROM sys.dm_os_memory_clerks WHERE pages_kb != 0 GROUP BY [type] ORDER BY total_pages_kb DESC;
SQL Server 中最主要的内存组件就是缓冲池(Buffer Pool, or Bpool)。缓冲池是一个 8KB 页面的集合,任何大于 8KB 的内存块都需要进行单独分配管理,例如 COM Objects、CLR Code、Extended Stored Procedures、Large Cached Plan 等。
缓冲区管理器(Buffer Manager)负责从磁盘上的数据文件中读取数据页(Data Page)和索引页(Index Page),将页数据放入 Buffer Pool 中作为数据缓存(Data Cache)。Buffer 指的是内存中的一个 Page,该 Page 的大小与数据页或索引页相同。
SQL Server 采用预读机制(Read-ahead)从磁盘中读取数据页和索引页至缓冲区中,以提高性能。预读机制完全有系统内部控制,不需要进行任何配置和调整。
数据缓存(Data Cache)
数据缓存(Data Cache)中使用哈希(Hashing)方式存储页数据。比如给定 DatabaseID-FileNumber-PageNumber 标识符,通过哈希函数将标识符存放到哈希表中。这样,通过哈希表(Hash Table)提供的快速检索功能,数据库引擎仅需要较少的内存读取就可以判断目标页是否存在于缓存中,如果不存在再从磁盘中读入缓存。
SQL Server 会将那些一段时间未被引用的页面缓冲位置标记为空闲页,通过空闲页链表进行管理。当需要新的缓冲页时,将从链表头获取要使用的页地址。
每个数据缓存的页中都包含一块表头区域,包含该页最后两次被引用的相关信息和状态信息,例如描述该页是否为脏页(Dirty Page)。脏页(Dirty Page)指的是从磁盘读取后的数据页被修改过。引用信息则用于实现数据缓存页的页面替换策略,它使用 LRU-K 算法。LRU-K 算法将有价值的缓冲区持有页保留在活跃缓冲池中,而如果缓冲区持有页的引用频率不高,则这些缓冲区页将被逐渐地释放回空闲缓冲区列表中。
LazyWriter 线程
SQL Server 为每个 NUMA 节点都创建了一个 LazyWriter 线程,用于扫描与该节点关联的缓冲区。LazyWriter 线程会进行周期性的睡眠和唤醒,当唤醒时将检查空闲缓冲区列表的长度,如果低于某个阈值,将扫描整个缓冲区。在扫描过程中,当发现页的引用率较低时,将检查脏页指示符。如果该页是脏页,则执行磁盘写入操作。然后该页将被释放回空闲缓冲区列表中。
SQL Server 动态使用内存时,必须不断地侦听可用内存的数量,并且追踪和监视内存的更改,以判断何时增加或减少自身的总内存量。每当 SQL Server 中的内存增加或减少 1MB,或达到服务器内存的 5% 时,将会产生事件通知。
检查点(Checkpoint)
检查点(Checkpoint)线程也定期扫描缓冲区,并将脏数据页写入磁盘。检查点与 LazyWriter 的区别在于,检查点不会向空闲缓冲区列表添加空闲缓冲区。检查点的唯一目的就是,确保将某一时刻前的页面都写入磁盘中,以便始终保持内存中的脏页数量最小。
SQL Server 将检查点的运行过程记录到事务日志中,当 SQL Server 出现故障时,由于已写入了某一时刻前的数据,可以减少恢复时间。
触发检查点的情况有:
- 手动触发 CHECKPOINT 命令在指定数据库上执行检查点。
- 日志正在变慢,超过容量的 70%。触发检查点可以截断日志并释放日志空间。
- 预计需要较长的恢复时间。预计恢复时间比设置的 "Recovery Interval" 选项的值要长时。如果 "Recovery Interval" 设置为 1,意味着检查点每分钟一次。默认值为 0,意味着 SQL Server 将选择合适的值,通常也是 1 分钟。
- 请求正常关闭 SQL Server,并且不使用 NOWAIT 选项。
检查点线程采用非顺序的方式对缓冲区进行扫描。当找到一个脏页时,会检查该脏页在磁盘上相邻的页面是否也是脏页,以便组合进行 gather-write 大块数据写入,提高性能。
计划缓存(Plan Cache)
在 Buffer Pool 的缓冲区的使用组件中,除了数据缓存(Data Cache),另一个使用量最大的就是对过程与查询计划的缓存,也就是通常说的计划缓存(Plan Cache)。
SQL Server 为处数据缓存外所有其他的缓存机制提供通用缓存框架,包括储存方式和资源监视器。储存方式包括三种:
- Cache Store:计划缓存(Plan Cache)和行集(Rowset Clerk)为常见的 Cache Store。
- User Store:元数据缓存(Metadata Clerk)即为一种 User Store。
- Object Store/Memory Pool:SNI Pooling Network Buffer 即为一种 Object Store。
Cache Store 和 User Store 采用 LRU 机制来分配和释放空间,使用 Clock 页面置换算法来实现。而 Object Store 则只是大块的内存,不需要 LRU 机制。
Cache Store 使用哈希表来加快查询速度,而 User Store 则未使用哈希表,Object Store 也未使用哈希表。
通过查看 sys.dm_os_memory_cache_clock_hands 视图,尤其是 removed_last_round_count 列,如果该值在急剧增加,那么是出现内存压力的显著征兆。
SELECT cache_address ,[name] ,[type] ,clock_hand ,clock_status ,removed_last_round_count FROM sys.dm_os_memory_cache_clock_hands;
Memory Broker
在 SQL Server 中有大量的组件需要使用内存,为了确保每个组件都在有效的使用内存,SQL Server 使用 Memory Broker 分析与内存消耗相关的行为,并改善动态内存分配。
Memory Broker 可以在 Buffer Pool、Query Executor、Query Optimizer 等各种使用缓存的组件间调度内存分配。通过监视内存的需求与消耗,通过带有反馈和改进机制的动态分配算法,协调各组件间形成最佳的内存分配方式。
SELECT * FROM sys.dm_os_ring_buffers WHERE ring_buffer_type = 'RING_BUFFER_MEMORY_BROKER';
观察内存
- sys.dm_os_memory_clerks 该视图描述 SQL Server 实例中正在使用内存的组件职责。
- sys.dm_os_memory_objects 该视图描述 SQL Server 当前分配的内存对象。
- sys.dm_os_memory_nodes 该视图描述分配的 NUMA Node 相关信息。
- sys.dm_os_memory_pools 该视图显示 Object Store 相关信息。
- sys.dm_os_memory_cache_counters 该视图描述 Cache Store 和 User Store 的运行情况快照。
- sys.dm_os_memory_cache_hash_tables 该视图描述活跃的缓存信息。
- sys.dm_os_memory_cache_clock_hands 该视图描述 Clock 页面置换算法的相关信息。
《人人都是 DBA》系列文章索引:
本系列文章《人人都是 DBA》由 Dennis Gao 发表自博客园个人技术博客,未经作者本人同意禁止任何形式的转载,任何自动或人为的爬虫转载或抄袭行为均为耍流氓。