简介:针对SQL Server 2008 R2多数据库环境下CPU与内存分配难题,这份文档围绕资源控制器展开,面向需要优化服务器性能的数据库管理员与运维人员。文档先梳理SQL Server 2005依赖独立实例与处理器亲和力的旧方案短板,再讲解虚拟机分配资源的局限,随后重点解析2008 R2资源控制器的设计思路:通过默认资源池与系统资源池定义CPU和内存的最小、最大百分比,确保关键负载获得保留资源,同时允许空闲资源跨池流动;内容还说明了工作负载组如何按请求特性分类转发,以及设置最大值后短暂CPU冲高仍属正常现象,避免管理员误判。资源共1个docx文件,压缩包仅84KB,轻量易读。已有1350人学习浏览,适合希望快速理解资源池、工作负载组配置逻辑的SQL Server使用者。文档还提示了资源控制器依赖脚本配置的缺点,并指引MSDN相关文章,节省自行摸索时间。
1. 装完不调等于白装:SQL Server 2008 R2 的 CPU 与内存优化分配到底在解决什么
网上还在大量搜 sql server 2008 r2 安装包下载、安装教程的人,大多把系统跑起来就不再管了,可问题恰恰从这里开始。SQL Server 2008 R2 安装完默认不限制任何资源:max degree of parallelism 为 0,意思是并行查询能吃满所有核;max server memory 默认接近无上限,意思是缓冲池会把物理内存吸干。于是最常见的一幕就是白天几个大查询把 CPU 打满、晚上内存被 Buffer Pool 占满导致 Windows 开始换页,最后只能重启实例。这个标题背后做的事,就是不动硬件、不改代码,靠 sp_configure、DMV 和性能计数器把并行度、内存边界、CPU 亲和性调成贴合业务的状态。适合刚接手老实例的 DBA、运维,以及准备给 2008 R2 做资源巡检的开发。
2. 先摸清家底:用 DMV 确认 2008 R2 当前的 CPU 与内存分配状态
2.1 一条 SQL 看懂当前 CPU 与物理内存
调参之前最怕的就是拿 "感觉" 当依据。比如一台物理 8 核 16 线程的机器,你在任务管理器里看到 16 个小格,就以为 SQL Server 有 16 个调度器,实际要看 SQLOS 自己的视角。SQL Server 2008 R2 里最直接能查这些信息的是sys.dm_os_sys_info,它记录的是当前实例启动时识别的硬件参数,比任务管理器靠谱得多。
-- 查看 SQL Server 2008 R2 当前识别的 CPU 与物理内存 SELECT cpu_count AS [逻辑CPU数], hyperthread_ratio AS [物理核/超线程比], physical_memory_kb / 1024.0 AS [物理内存MB] FROM sys.dm_os_sys_info;这段查询输出三个关键值:cpu_count是 SQLOS 看到的逻辑处理器数量,对应调度器个数;hyperthread_ratio是物理核与逻辑核的换算关系,比如 8 物理核 16 逻辑线程时它是 16;physical_memory_kb是操作系统报告给 SQL Server 的物理内存。注意在虚拟机里,cpu_count显示的是 vCPU 数而不是宿主机的物理核数,后续设 maxdop 必须按这个值来,不能按宿主机配置理解。
很多 2008 R2 实例是从 2005 或 2000 升级上来的,这台机器可能经历过加内存、改 vCPU 配额,但 SQL Server 的配置一直是当年装机时的默认值。所以这条 SQL 的意义不是看热闹,而是把「SQL Server 眼中的硬件」和「你以为的硬件」对齐。查询结果如果cpu_count是 32,但你知道这台机器只有 12 物理核,就要意识到虚拟化层开了超线程或者给了超额 vCPU,后面的并行度设置就要小心。
2.2 把六个资源相关的配置项一次性列出来
摸清硬件后,接着看实例层配置。SQL Server 2008 R2 把资源类配置存在sys.configurations里,但很多人只查value列就下结论,忘记了value_in_use才是真正生效的值。如果某次 sp_configure 改完没执行 RECONFIGURE,两列就会不一致,这时候按value判断就会踩坑。
-- 查看当前资源分配相关的实例配置 SELECT name, value AS [配置值], value_in_use AS [运行值], minimum, maximum, is_dynamic AS [是否动态生效] FROM sys.configurations WHERE name IN ('max degree of parallelism', 'min server memory (MB)', 'max server memory (MB)', 'affinity mask', 'affinity64 mask', 'awe enabled') ORDER BY name;min server memory (MB)的默认值是 0,max server memory (MB)的默认值是 2147483647,也就是不设上限;max degree of parallelism默认 0,表示使用全部逻辑处理器;affinity mask和affinity64 mask默认也都是 0,表示不固定调度器到特定 CPU。is_dynamic为 1 表示这个配置修改后不用重启 SQL Server 服务就生效,2008 R2 里的内存和并行度选项基本都是动态的,但没有执行 RECONFIGURE 之前,value_in_use不会变,这是新手最容易忽略的操作链。
把查询结果和默认值比对,就能快速判断这台实例是不是「裸奔状态」:maxdop 为 0、max server memory 是 2147483647、affinity mask 为 0,三件事凑齐,基本可以确认资源分配从没被认真调过。接下来第 3 章和第 4 章的所有改动,都是在为这种裸奔状态装上边界。
3. 把 CPU 优化分配做实:maxdop 与亲和性的调参路径
3.1 maxdop 的设置逻辑:先分清楚负载是 OLTP 还是 OLAP
max degree of parallelism控制的是单个并行查询最多能占用多少个调度器。SQL Server 2008 R2 的查询优化器发现某个语句适合并行执行时,会按这个上限去分配 worker 线程。设成 0 等于不设限,这在数据量小的机器上没有感觉,但一旦出现几条大查询并发,就会出现所有 CPU 被并行扫描占满、连登录请求都排队的场面。对正在跑业务的 OLTP 系统来说,这比查询本身慢几秒更致命。
先确定这台实例承载的业务类型再定值,不要照抄别人的配置。我的做法分三档:纯 OLTP 系统,maxdop 设置在 4 以内,保证有足够调度器处理小查询;混合负载,设置在 4 到 8 之间,给报表查询留并行空间但也留出余量;纯 OLAP 或数据仓库,且并发用户数不多,可以设置在 8 到 16。16 核以下的机器,经验推荐不要超过核数的一半。
| 负载类型 | 逻辑 CPU 数 | maxdop 建议 |
|---|---|---|
| 纯 OLTP | 8 | 2~4 |
| 混合负载 | 16 | 4~8 |
| 纯 OLAP / 报表 | 16 | 8~16 |
| 虚拟化环境(vCPU 超配) | 8~32 | 2~4 |
设置命令如下,注意两个前提:先打开高级选项,改完必须 RECONFIGURE。
-- 1. 开放高级配置选项 EXEC sp_configure 'show advanced options', 1; RECONFIGURE; GO -- 2. 把并行度上限设为 4,按上面表格按需调整 EXEC sp_configure 'max degree of parallelism', 4; RECONFIGURE; GO -- 3. 确认改动已生效 EXEC sp_configure 'max degree of parallelism';这里解释几个参数细节。show advanced options必须最先设置为 1,否则 max degree of parallelism 这类选项会被拒绝;RECONFIGURE 的作用是把配置值写入运行态,缺少这一步会出现第 5 章要讲的「改了不生效」问题。maxdop 是动态配置,执行后不需要重启服务,但如果实例上有正在运行的并行查询,新值只对后续编译的语句生效,已经跑起来的语句不会中断。
改完后建议顺手看一个等待类型:CXPACKET。它表示并行查询在等待子线程同步,少量存在是正常的,但如果优化前 CXPACKET 排在sys.dm_os_wait_stats的前三名,说明并行度过高反而制造了大量协同开销。maxdop 调低后这个等待通常明显下降,这是验证调整是否有效的直观指标。
3.2 affinity:什么时候真的需要绑核,什么时候千万别碰
affinity mask决定 SQLOS 的调度器允许跑在哪些逻辑 CPU 上,本质是把 SQL Server 线程和特定 CPU 绑定。很多刚接触性能调优的人看到服务器上还有空闲核,就想用 affinity 把实例隔离开,这其实是用错了场景。对单实例部署而言,绑核很少带来收益,反而可能让 SQL Server 在 CPU 0 上和其他系统进程抢资源。
真正该用 affinity 的场景是单机跑多个 SQL Server 实例,或者 SQL Server 和其他高负载应用混部,需要硬性划分 CPU 边界。SQL Server 2008 R2 提供了两种做法:老的 sp_configure 里的 affinity mask(32 位用 affinity mask,超过 32 逻辑 CPU 用 affinity64 mask),以及更直观的 ALTER SERVER CONFIGURATION。64 位实例上,更推荐用后者,它支持 CPU = 0 TO 7 这样的区间写法,不用自己做二进制到十进制的换算。
-- 多实例隔离场景:把当前实例绑定到 CPU 0~7 ALTER SERVER CONFIGURATION SET PROCESS AFFINITY CPU = 0 TO 7; GO -- 查看当前 CPU 亲和性 EXEC sp_configure 'affinity mask'; GO -- 取消绑定,恢复自动调度 ALTER SERVER CONFIGURATION SET PROCESS AFFINITY CPU = AUTO;参数说明:CPU = 0 TO 7 表示逻辑 CPU 编号 0 到 7,闭区间,共 8 个核;CPU = AUTO 取消手动绑定。执行 ALTER SERVER CONFIGURATION SET PROCESS AFFINITY 后,SQL Server 会自动把位掩码同步到 affinity mask 配置项,所以用 sp_configure 查看也能确认。如果服务器是 NUMA 架构,绑核最好按 NUMA 节点划分,比如第一个节点 0~7、第二个节点 8~15,不要让一个实例的调度器横跨两个节点还同时访问远端内存。
单实例 + 无其他高负载进程时,保持默认 0 就是最优解。现在新 CPU 都在讲智能核心调度,但 SQL Server 2008 R2 的 SQLOS 并不认识这套机制,它只认逻辑处理器数量。给它绑死几个核,反而把系统调度器的灵活性废掉了。
4. 把内存优化分配做实:max server memory 的计算与内存去向排查
4.1 计算 max server memory:给操作系统和其他进程留够余量
SQL Server 的内存机制有个特性:只要 max server memory 不设限,它就会不断把空闲物理内存吸入 Buffer Pool,因为它默认「内存闲着也是浪费,不如用来缓存数据页」。对数据库本身这没错,但服务器不只是 SQL Server 一个人的。操作系统需要内存做文件缓存和驱动缓冲,Windows Defender 这类安全软件的 Antimalware Service Executable 进程也会随时占用几百 MB 到 1 GB 内存,再加上备份代理、监控 agent,内存被截胡后 Windows 就开始疯狂换页。
计算 max server memory 的常见做法是按物理内存留出固定余量:8GB 以内的小机器,给 OS 留 2GB;8GB 到 64GB 的机器,留 4GB;超过 64GB,余量建议控制在 8GB 左右,同时观察任务管理器里非 SQL 进程的实际占用。如果这台机器上还跑着其他业务进程,要额外减掉它们的内存配额。只留 2GB 给 64GB 内存的服务器看似合理,但一旦安全软件扫描仓库文件,就可能触发内存压力。
-- 以物理内存 64GB 为例,SQL Server 上限设为 56320MB(约 55GB) EXEC sp_configure 'min server memory (MB)', 2048; EXEC sp_configure 'max server memory (MB)', 56320; RECONFIGURE; GO -- 验证内存配置 EXEC sp_configure 'max server memory (MB)';这里要解释 min server memory 的作用,它经常被误解。min server memory (MB)不是启动时立刻抢占 2GB,而是表示 SQL Server 在内存压力下会努力让 Buffer Pool 保持在这个水位以上。换句话说,它是在告诉内存管理器:低于这个值就别把缓存页交出去。真正决定内存上限的是 max server memory,SQL Server 启动时只吃少量内存,随着数据页加载逐步增长到这个硬边界,不会再突破。
配置里的单位是 MB,很多人第一次改会把它当 GB 填,导致设了一个超大的值,然后发现 SQL Server 内存还在继续涨,以为配置没生效。实际上 56320 MB 就是 55GB,如果直接填 56320 还嫌大,说明把单位理解反了。改完内存配置后观察任务管理器里的 sqlservr.exe 工作集会缓慢上升,直到接近上限,这才是正常现象。
4.2 内存都去哪了:用 sys.dm_os_memory_clerks 追内存吃相
内存上限设好以后,还要知道内存是被谁吃掉的。SQL Server 进程内的内存并不只有 Buffer Pool 一块,查询编译缓存、锁管理器、CLR、连接池都要占内存。2008 R2 里可以通过sys.dm_os_memory_clerks按类型汇总,一眼看出哪些内存吃相异常。
-- 按内存 clerk 类型汇总进程内内存占用 SELECT type, SUM(pages_kb) AS [物理内存KB], SUM(virtual_allocated_kb) AS [虚拟内存KB] FROM sys.dm_os_memory_clerks GROUP BY type ORDER BY [物理内存KB] DESC;结果里最常看到的几类:MEMORYCLERK_SQLBUFFERPOOL是数据页缓冲池,通常占比最大,属于正常;MEMORYCLERK_SQLCACHE是缓存计划的内存,如果业务里有大量不重用的 ad-hoc 查询,这一项会异常大;OBJECTSTORE_LOCK_MANAGER是锁内存,它暴涨往往说明有长时间未提交事务或锁升级风暴。如果 Buffer Pool 之外的项目加起来超过总内存的 20%,先不要急着加大 max server memory,而是先处理异常的 clerk。
更直接判断内存压力的指标是Memory Grants Pending,它表示有多少查询在等待内存授权。这个计数器长期大于 0,说明内存不够分,查询在 RESOURCE_SEMAPHORE 等待上排队。注意这个计数器是累积值,不是瞬时值,要看趋势,不能只看某一秒的结果。
-- 查看内存授权等待数量,持续上涨说明内存候选不够 SELECT cntr_value AS [Memory Grants Pending] FROM sys.dm_os_performance_counters WHERE object_name LIKE '%Memory Manager%' AND counter_name = 'Memory Grants Pending';如果Memory Grants Pending明显上涨,同时第 4.1 节的余量已经留够,优先检查有没有查询请求了超大的 sort/hash 内存。一次几百万行的排序可能就要 5GB 内存授权。用sys.dm_exec_query_memory_grants可以追到具体是哪条语句在申请内存,这类问题靠调 max server memory 解决不了,得改语句或加索引。顺带提一句,排查进程内内存泄漏时,很多人习惯用 poolmon 查内核池,但 SQL Server 的内存是在进程内管理的,用 DMV 定位比 poolmon 有效得多。
4.3 别忘了 32 位实例的 AWE 边界
2008 R2 是还能碰到 32 位实例的最后一个版本,如果你维护的机器是 32 位系统,内存分配的逻辑完全不同。32 位进程的用户模式地址空间通常只有 2GB 到 4GB,即使物理内存有 16GB,SQL Server 默认也够不着。部分 32 位版本支持 AWE(Address Windowing Extensions)扩展内存寻址,但需要在 sp_configure 里开启,并给 SQL Server 服务账户授予 Lock Pages in Memory 权限。
-- 仅 32 位实例需要启用 AWE;64 位实例开启没有意义 EXEC sp_configure 'awe enabled', 1; RECONFIGURE WITH OVERRIDE; GO -- 确认实例位数与版本 SELECT SERVERPROPERTY('Edition') AS [版本], SERVERPROPERTY('InstanceName') AS [实例名];这里特别说明 RECONFIGURE WITH OVERRIDE 的用法。awe enabled 属于高风险选项,普通 RECONFIGURE 可能被拒绝,需要带上 WITH OVERRIDE 强制生效。但这个选项只在 32 位实例上有意义,64 位实例里开启 AWE 不会带来任何内存扩展效果,反而可能引起不必要的误解。判断实例是 32 位还是 64 位,看 Edition 输出里有没有 x86 字样,或者直接看任务管理器里 sqlservr.exe 路径,x86 目录就是 32 位。
对 32 位实例,最稳妥的优化建议不是开 AWE,而是尽早计划迁移到 64 位实例。AWE 分配的内存在数据页缓冲上有效,但查询编译缓存、连接内存等仍然受限,内存配置的天花板就在那里。第 5 章的避坑内容里,我会展开讲一个典型现象:32 位实例把 max server memory 调大后,实际用不满。
5. 避坑:SQL Server 2008 R2 资源分配最常见的 5 个翻车现场
5.1 现象:maxdop 调小以后,报表查询反而更慢
一位同事把 maxdop 从默认 0 调到 4,OLTP 业务是稳了,但第二天业务方反馈月底报表跑得比原来慢了一半。查看实际执行计划,发现一条几千万行的事实表聚合原本走 8 路并行扫描,现在被限制成 4 路,并行分区扫描的收益直接被砍掉一半。
原因:maxdop 是全局配置,它对所有查询一视同仁,OLTP 需要限制并行度避免小查询被挤兑,但大型报表恰恰需要并行度来缩短响应时间。全局配置不可能同时满足这两种诉求。
解决:分清负载主体。全局 maxdop 按 OLTP 需求设 4,对少数需要大并行的报表语句单独加查询提示。SQL Server 2008 R2 支持OPTION (MAXDOP 8)查询提示,让这一条语句突破全局限制。注意 2008 R2 的查询提示写法要放在语句末尾:
SELECT COUNT(*) AS [记录数] FROM dbo.FactSale WITH (NOLOCK) WHERE SaleDate >= '2024-01-01' OPTION (MAXDOP 8);5.2 现象:内存配置没动,Windows 突然卡死,事件日志报 17890
服务器 64GB 内存,SQL Server 一直是默认配置,运行半年后某天上午业务高峰期 Windows 突然卡死,远程桌面都敲不动命令。重启后查看系统事件日志发现多条 17890 事件,提示 SQL Server 正在对 Buffer Pool 执行内存回收。再配合性能监视器一看 Available MBytes 几乎归零,页面文件使用率冲高。
原因:max server memory 保持默认 2147483647,SQL Server 把 64GB 物理内存几乎全部吸入 Buffer Pool,Windows 和其他进程需要内存时只能靠换页。尤其是 Antimalware Service Executable 这类安全进程在扫描文件时会突然申请大块内存,直接触发系统级内存压力。
解决:立刻把 max server memory 调到合理值,比如 64GB 物理内存设 56320MB,然后重启 SQL Server 服务让 Buffer Pool 收缩。这里注意,即使 SQL Server 支持动态调整,释放已占用的内存也需要重启服务或等待内存压力逐步回收,生产环境不能等,直接计划维护窗口重启。
5.3 现象:32 位实例,max server memory 设了 16GB,进程却只用到 2GB
一台 32 位 SQL Server 2008 R2 的物理内存有 16GB,DBA 把 max server memory 设成 16384MB,想着给数据库多分点内存,结果 perfmon 里 sqlservr.exe 的工作集始终在 2GB 附近,Buffer Cache Hit Ratio 也上不去。
原因:32 位进程默认地址空间只有 2GB(/3GB 开关后可到 3GB 左右),普通内存模式根本够不到 16GB。AWE 没有开启,服务账户也没有 Lock Pages in Memory 权限,设再大的 max server memory 也只是个数字。
解决:先用SELECT SERVERPROPERTY('Edition')确认是不是企业版,非企业版的 32 位实例无法使用 AWE 超过 4GB;如果是企业版,开启 awe enabled 并到本地安全策略里给 SQL Server 服务账户授予「锁定内存页」权限,之后重启服务。长期方案只有一个:迁移到 64 位实例。32 位上的 AWE 只是补丁,不是出路。
5.4 现象:配了 affinity mask 后 CPU 出现一条平线,业务反而变慢
某服务器为了隔离 SQL Server 和其他应用,用 sp_configure 把 affinity mask 设为 15,也就是只允许 CPU 0 到 3 跑 SQL Server。改完后任务管理器里出现明显两极分化:CPU 0 到 3 打满,CPU 4 到 15 全是绿油油的空闲,业务响应时间却变长了。
原因:SQL Server 绑在 0 到 3 号核上,但 Windows 的时钟中断、网卡中断、防病毒扫描也大部分落在这些低编号核上。SQLOS 明明看到 4 个核很忙,却碰不到 CPU 4 到 15 的空闲资源,大量线程在调度器队列里排队。CPU 看起来有富余,实际上 SQL Server 能用的只有那 4 个核。
解决:单实例别用 affinity,这是最直接的结论。多实例隔离属于例外场景,但 2008 R2 上应该用ALTER SERVER CONFIGURATION来绑定,而不是手工算二进制掩码。如果已经踩坑,执行ALTER SERVER CONFIGURATION SET PROCESS AFFINITY CPU = AUTO退回自动调度。改完后重启一下 SQL Server 服务,让调度器重新分布。
5.5 现象:sp_configure 改完不生效,重启后配置回滚
有 DBA 执行了EXEC sp_configure 'max server memory (MB)', 40960,没有报错,但第二天查看配置,发现 value_in_use 还是 2147483647。还有一些情况是改完 min server memory 之后 SQL Server 服务直接起不来。
原因分两种。第一是改完没执行 RECONFIGURE,所有 sp_configure 的改动在 RECONFIGURE 之前只停留在 value 列,没有进入运行态,报错都没有。第二是 min server memory 设得过大、超出物理内存,导致 SQL Server 启动时尝试提交内存失败。这种情况常见于物理内存只有 8GB 却把 min 设为 16384MB。
解决:养成「改完必 RECONFIGURE、查必看 value_in_use」的习惯。如果服务已经起不来,用命令行方式以最小配置模式启动 SQL Server 实例,把 min server memory 降回安全值:
# 以前台最小配置方式启动 SQL Server 实例,适合单机救援 sqlservr -f -s MSSQLSERVER等实例起来后,立刻执行 sp_configure 把 min server memory 改回 0 或合理值,再恢复正常服务启动方式。这个操作属于灾后救援,尽量选在维护窗口做,因为单用户模式下业务不可用。配置类的后悔药就在这一步,改坏了不要慌,最小配置模式是第一选择。
6. 验证优化效果:用 DMV 写一份 2008 R2 的资源分配验收脚本
6.1 三个最该看的资源健康信号
调完参数不能只看任务管理器,要回到 SQL Server 自己的性能计数器。最核心的三个信号:Buffer Cache Hit Ratio 反映缓存命中率,通常建议长期在 95% 以上;Page Life Expectancy 反映数据页在缓冲池里的平均存活秒数,低于 300 说明内存压力大或者缓存被频繁冲刷;Processor Queue Length 如果持续高于每个核心 2,说明 CPU 已经排队。
-- 缓存命中率(0~100) SELECT cntr_value AS [BufferHitPercent] FROM sys.dm_os_performance_counters WHERE object_name LIKE '%Buffer Manager%' AND counter_name = 'Buffer cache hit ratio'; -- 数据页平均存活秒数 PLE,低于 300 需要警惕 SELECT cntr_value AS [PLE_秒] FROM sys.dm_os_performance_counters WHERE object_name LIKE '%Buffer Manager%' AND counter_name = 'Page life expectancy';6.2 我的验收节奏:基线先行,改完 30 分钟后看趋势
我的习惯是改配置前先跑一轮基线,存成一张表,调完 30 分钟后再抓一轮。不要盯着瞬时值,趋势才是真实结果。内存的变更通常要跑一天才能看出稳定水位,maxdop 的变更则在业务高峰最能体现。
这些年调过的 2008 R2 实例多了,我给自己定了一条规矩:任何一次资源分配变更,都必须当场记录下行和恢复方案,顺便把 baseline 数据放到一张维护表里备忘。不然三个月后同事问起「这台机器的内存上限是谁定的」,现场又是一阵玄学排查。这篇方案只针对 2008 R2 的逻辑,放在现在的新版本依然通用,换汤不换药。希望帮到你。
本文还有配套的精品资源,点击获取