如何在香港服务器的Windows系统中调整Page File策略,解决数据库高负载下的内存溢出问题?

凌晨两点,香港葵涌的数据中心一层,压缩机的嗡鸣声跟着警报灯的闪烁一起压在我耳朵里。监控墙上 SQL Server 的告警像瀑布一样刷下来:There is insufficient system memory to run this query (Error 701)。
我捧着纸杯咖啡,盯着那台最“金贵”的数据库主机——一台跑 Windows Server 的独立物理机。两小时前业务做了一次促销推送,读写流量飙升,我原以为是索引没走对或某个慢查询暴涨,结果进机房一看,不是 CPU 打满,也不是磁盘飚红,是“提交内存 (Committed Bytes)”硬杠“提交上限 (Commit Limit)”。
这事儿我吃过亏:很多人把“内存不足”简单理解为“物理内存满了”,可在 Windows 的世界里,只要已提交内存逼近提交上限,就会 OOM(哪怕物理内存还剩)。而这个“提交上限”,就跟 Page File(分页文件) 的策略绑在一起。
那一刻我就知道,今晚要从 Page File 下手救火。
环境与问题画像(现场参数)
| 项 | 详情 |
|---|---|
| 机房 | 香港(HK) |
| 服务器 | AMD EPYC 7443P(24 核),128 GB RAM |
| 存储 | C: 240 GB SATA SSD(系统盘);E: 1.92 TB NVMe(数据);F: 960 GB NVMe(日志);T: 480 GB NVMe(tempdb) |
| 操作系统 | Windows Server 2019 Datacenter |
| 数据库 | Microsoft SQL Server 2019 Enterprise(启用 LPIM:Lock Pages In Memory) |
| 工作负载 | 高并发混合 OLTP/OLAP;突发大查询 + 批处理 |
| 现象 | 促销高峰期出现 Error 701;Windows 事件 2004(资源耗尽);Committed Bytes ≈ 97% Commit Limit |
| Page File 现状 | 被之前的同事“优化”成 禁用(0 MB),怕影响磁盘性能 |
| SQL 配置 | max server memory = 110 GB,为 OS 预留 ~18 GB |
关键监控(促销高峰期)
| 指标 | 值(故障前) | 备注 |
|---|---|---|
| Memory\Committed Bytes | 124 GB | 已提交内存 |
| Memory\Commit Limit | 128 GB | 无 Page File 时约等于物理内存 |
| % Committed Bytes In Use | 96.9% | 接近爆线 |
| Memory\Available MBytes | 1.1 GB | 并非 0,但没意义 |
| Paging File% Usage | 0% | 因为没 PF |
| Process(sqlservr)\Working Set | 108 GB | 与 max server memory 接近 |
| 事件日志 | Event ID 2004 | 资源耗尽 |
判断: 已提交内存逼近提交上限(Commit Limit=物理内存 + Page File),根因是禁用 Page File 导致提交上限过低。LPIM 让 SQL Server 不轻易被换出,但并不减少系统的“提交”需求;当其他进程、驱动或内核分配提交内存,仍然会撞上上限。
策略总览:Page File 的三条铁律(我在现场反复验证过)
1. 不要禁用 Page File。
- 禁用只在“理论纯内存工作负载”里看起来优雅,现实里反而收窄 Commit Limit,导致更早 OOM。
2. 把主要的 Page File 放在最快、最空闲的盘(优先 NVMe),但 C: 保留一个小的(用于转储/回退)。
- C: 保留 1–2 GB 固定大小。
- 主 Page File 放到 E:(数据 NVMe)或专用 NVMe,固定 16–64 GB(按内存与峰值提交量评估)。
- 不要放在日志盘 F:(避免与顺序写竞争);tempdb 盘 T: 也尽量别(防止紧急情况下互相拖累)。
3. 固定大小(Initial=Maximum),减少碎片与抖动。
- 系统托管(自动)在部分场景下会临时扩缩,高峰扩容瞬间最危险;固定区间更稳。
计算与选型:这台 128 GB 机器我给的 Page File 方案
- 我怎么估 Page File(给你一套可复用的思路)
- 先看峰值提交占比:高峰 Committed Bytes ≈ 124 GB。
- 我希望安全余量 ≥ 20%:124 GB / X ≤ 80% → X ≥ 155 GB。
- 物理内存 128 GB → 需要的 Page File 最少 ≈ 27 GB 才能把 Commit Limit 拉到 ~155 GB。
- 结合 LPIM 与 NVMe 写延迟,我选 E: 上固定 32 GB。
- C: 留 2 GB 小 Page File,保证内核转储与特殊回退。
经验折中表(SQL/大内存应用,可参考):
- RAM ≤ 32 GB:PF ≈ 1.0×RAM(最少 8 GB,最多 48 GB)
- 64–256 GB:PF ≈ 16–32 GB +(超过 64 GB 部分的 10%)
- ≥ 256 GB:PF ≈ 32–64 GB
目标是让 Commit Limit ≥ 峰值提交 × 1.2,同时不把 PF 做到离谱(避免无意义的磁盘占用与崩溃时长 dump)。
变更步骤(完整可复用 Runbook)
变更前快照(我在现场先跑了这个)
# 记录关键内存/提交/分页文件状态(保存证据)
Get-Counter '\Memory\Committed Bytes','\Memory\Commit Limit','\Memory\Available MBytes','\Paging File(_Total)\% Usage' `
-SampleInterval 1 -MaxSamples 5 | Format-List
Get-CimInstance -ClassName Win32_OperatingSystem | Select-Object TotalVisibleMemorySize,TotalVirtualMemorySize
Get-CimInstance -ClassName Win32_PageFileSetting
Get-CimInstance -ClassName Win32_PageFileUsage
(Get-CimInstance Win32_ComputerSystem).AutomaticManagedPagefile
1)规划与分区选择
C: 仅系统,保留 2 GB 固定 PF。
E: NVMe(数据盘 IO 富余),设置 32 GB 固定 PF。
确认 F: 日志盘 与 T: tempdb 不承载 PF。
坑 1(我踩过): 一味把 PF 放 C:,遇到内核转储 + 大流量时,系统盘 IOPS 顶不住。
坑 2: 把 PF 放日志盘,日志延迟抖动极其敏感,夜深人静会被电话叫起来。
2)关闭系统托管并创建固定 Page File
WMIC 在新系统里属“弃用不废”,图形界面可做,但我在机房直接 PowerShell + 注册表更稳且可审计。
方案 A:WMIC(快速)
wmic computersystem where name="%COMPUTERNAME%" set AutomaticManagedPagefile=False
rem 清理历史配置(若有)
wmic pagefileset where name="C:\\pagefile.sys" delete
rem C: 小 PF
wmic pagefileset create name="C:\\pagefile.sys"
wmic pagefileset where name="C:\\pagefile.sys" set InitialSize=2048,MaximumSize=2048
rem E: 主 PF
wmic pagefileset create name="E:\\pagefile.sys"
wmic pagefileset where name="E:\\pagefile.sys" set InitialSize=32768,MaximumSize=32768
方案 B:注册表(更可控、易配置化)
$reg = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management'
# 关闭系统托管
Set-ItemProperty -Path $reg -Name 'ExistingPageFiles' -Value @()
Set-ItemProperty -Path $reg -Name 'PagingFiles' -Type MultiString -Value @(
'C:\pagefile.sys 2048 2048',
'E:\pagefile.sys 32768 32768'
)
Set-ItemProperty -Path $reg -Name 'TempPageFile' -Value 0
Set-ItemProperty -Path $reg -Name 'ExistingPageFiles' -Type MultiString -Value @(
'C:\pagefile.sys',
'E:\pagefile.sys'
)
# 自动托管开关(有的环境在别处跟踪)
wmic computersystem where name="%COMPUTERNAME%" set AutomaticManagedPagefile=False
提醒:需要重启生效。我当场和业务沟通,挑了 10 分钟窗口。
3)(可选)内核转储与专用转储文件
保留 C: 小 PF 的意义之一就是转储。如果你想把转储放到非系统盘:
$crash = 'HKLM:\SYSTEM\CurrentControlSet\Control\CrashControl'
# 3 = Kernel memory dump;1 = Complete;2 = Small
Set-ItemProperty -Path $crash -Name 'CrashDumpEnabled' -Value 3
# 修改转储路径
Set-ItemProperty -Path $crash -Name 'DumpFile' -Value 'E:\MEMORY.DMP'
# 可设置专用转储文件,避免要求巨大的 C:\pagefile.sys
Set-ItemProperty -Path $crash -Name 'DedicatedDumpFile' -Value 'E:\dedicated_dump.sys'
坑 3: 把 C: 的 PF 彻底删掉,某些情况下会导致内核转储失败或临时创建临时 PF,反而更不可控。
4)重启与变更后校验
# 变更后确认:Commit Limit 是否已拉升?
Get-Counter '\Memory\Committed Bytes','\Memory\Commit Limit','\Paging File(_Total)\% Usage' -SampleInterval 1 -MaxSamples 5
Get-CimInstance -ClassName Win32_PageFileSetting | Select-Object Name,InitialSize,MaximumSize
Get-CimInstance -ClassName Win32_PageFileUsage | Select-Object Name,AllocatedBaseSize,CurrentUsage,PeakUsage
(Get-CimInstance Win32_ComputerSystem).AutomaticManagedPagefile
我当天看到 Commit Limit ≈ 162 GB(128 GB RAM + 34 GB PF 的量级),% Committed Bytes In Use 从 97% 掉到 82% 左右,页文件峰值占用 12% 左右,E: 的平均写延迟仍稳在 1–2 ms。
与数据库配置的配合(关键但容易被忽略)
LPIM(Lock Pages In Memory):启用后能避免 sqlservr 工作集被换出,但并不降低系统的提交需求。所以 LPIM ≠ 可以禁用 Page File。
max server memory:有 PF 后也别贪心。我依旧保持 110 GB,为 OS、驱动、备份代理、杀软/EDR 留足空间。
观察 RESOURCE_SEMAPHORE 等等待类型,必要时在查询层面优化内存授予(grant),这能进一步减少对 Commit Limit 的挤压。
结果对比(我当晚拉了 4 小时窗口)
| 指标 | 调整前 | 调整后 | 变化 |
|---|---|---|---|
| Commit Limit | 128 GB | ≈162 GB | ↑ +34 GB |
| Committed Bytes(峰值) | 124 GB | 133–136 GB | 负载更猛也不炸 |
| % Committed Bytes In Use | 96.9% | 82–85% | 安全余量充足 |
| Paging File% Usage(峰值) | 0% | 10–15% | 有效兜底 |
| E: 磁盘写延迟 | 1–2 ms | 1–2 ms | 基本无感 |
| SQL 701 | 频繁 | 无 | 归零 |
| 事件 2004 | 多发 | 无 | 归零 |
你可能会踩的坑(都是我和同事踩过的)
- 只在 C: 放超大 PF:系统盘 IOPS/带宽差,撞到大写入会拖慢一切。
- 日志盘放 PF:OLTP 日志对延迟敏感,PF 抢 IO 就是灾难。
- 忘了重启:注册表改了不重启,实测不生效。
- 自动托管没关干净:有的环境组策略/工具会又把它开回去,记得巡检 AutomaticManagedPagefile。
- 监控维度只看“可用内存”:必须把 Committed Bytes / Commit Limit 加进看板。
- PF 设太大:没有必要做成几百 GB,转储时间、备份/还原清理都难受;重点是够用 + 固定。
- 忘记 C: 小 PF:会影响内核转储的稳定性和某些极端回退路径。
一键化脚本(我后来做成了变更模板)
按需要调整盘符与大小;执行后重启。
param(
[int]$OsPFMB = 2048,
[int]$MainPFMB = 32768,
[string]$MainDrive = 'E',
[ValidateSet('Small','Kernel','Complete')]
[string]$Dump = 'Kernel',
[switch]$Reboot
)
$regMM = 'HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Memory Management'
$regCrash = 'HKLM:\SYSTEM\CurrentControlSet\Control\CrashControl'
# 1) 关闭自动托管
wmic computersystem where name="%COMPUTERNAME%" set AutomaticManagedPagefile=False
# 2) 设置固定 Page File(C: + 主盘)
$paging = @("C:\pagefile.sys $OsPFMB $OsPFMB", "$MainDrive`:\pagefile.sys $MainPFMB $MainPFMB")
Set-ItemProperty -Path $regMM -Name 'PagingFiles' -Type MultiString -Value $paging
Set-ItemProperty -Path $regMM -Name 'ExistingPageFiles' -Type MultiString -Value @("C:\pagefile.sys","$MainDrive`:\pagefile.sys")
Set-ItemProperty -Path $regMM -Name 'TempPageFile' -Value 0
# 3) 转储类型
$dumpMap = @{ Small = 2; Kernel = 3; Complete = 1 }
Set-ItemProperty -Path $regCrash -Name 'CrashDumpEnabled' -Value $dumpMap[$Dump]
Set-ItemProperty -Path $regCrash -Name 'DumpFile' -Value "$MainDrive`:\MEMORY.DMP"
Set-ItemProperty -Path $regCrash -Name 'DedicatedDumpFile' -Value "$MainDrive`:\dedicated_dump.sys"
Write-Host "Configured paging files:" -ForegroundColor Cyan
Get-CimInstance -ClassName Win32_PageFileSetting | Select Name,InitialSize,MaximumSize | Format-Table
if ($Reboot) { shutdown /r /t 0 }
监控与回归测试(我当晚是这么盯的)
PerfMon 加以下计数器:
- Memory\Committed Bytes、Memory\Commit Limit、Memory\% Committed Bytes In Use、Paging File(_Total)\% Usage、PhysicalDisk(*)\Avg. Disk sec/Write。
事件日志:
- Get-WinEvent -FilterHashtable @{LogName='Microsoft-Windows-Resource-Exhaustion-Detector/Operational'; Id=2004} -MaxEvents 20
SQL 侧:
- 观察 RESOURCE_SEMAPHORE、PAGEIOLATCH_* 等等待。
关键 DMV:
-- 进程级内存与锁页分配
SELECT physical_memory_kb, locked_page_allocations_kb, page_fault_count
FROM sys.dm_os_process_memory;
-- 系统内存快照
SELECT * FROM sys.dm_os_sys_memory;
回归压测:复现高峰典型读写模式,重点看 Commit Ratio 是否稳定在 80–85% 以内。
FAQ:几件容易混淆的事(我给团队做培训时的笔记)
“有 LPIM 就可以不要 Page File?”——不行。LPIM 只是保护 SQL 的工作集,系统提交上限还是需要 PF 来抬高。
“Page File 会拖慢性能吗?”——在 NVMe 上固定大小、且平时只承接少量冷页与 commit 兜底时,影响极小。若 PF 百分比经常 50%+,说明根因是内存配置不足或查询内存授予过大,应优化 SQL 或加内存。
“Page File 要分散到多个盘吗?”——如果是不同物理 NVMe,可以分摊(例如 E: 24 GB + G: 24 GB),Windows 会按比例使用;如果只是同一物理池的不同分区,收益有限。
“一定要 Complete Dump 吗?”——生产通常 Kernel Dump 足够,Complete Dump 要求 C: PF ≥ 物理内存,成本极高。
静下来的风扇与更“宽”的余量
重启后,我又在机房里站了十分钟。风扇的声音不再压迫,E: 上的写入曲线稳得像心电图的等电位线。第二波流量上来时,我看着 Committed Bytes / Commit Limit 稳稳卡在 0.83 左右,告警板没有再红起一片。
我把那杯已经凉透的咖啡丢进垃圾桶,心里很清楚:今晚不是靠某个“玄学优化”救的火,而是把 Windows 的内存提交模型和 Page File 机制重新按到位了。下一次来香港机房,我大概率还是会顺手摸一下 E: 的温度,只是这次,多半只是出于习惯。
TL;DR(给未来的我/你)
- 别禁用 Page File;C: 留 2 GB,最快的 NVMe 盘做 16–64 GB 固定。
- 目标:Commit Limit ≥ 峰值提交 × 1.2;监控 Committed Bytes / Commit Limit。
- 固定大小,避免自动扩容时刻“踩雷”。
- 别把 PF 放日志盘;和 tempdb 尽量分开。
- 配合 max server memory 与查询内存授予优化。
- 变更要重启,并做前后对比与压测回归。
如果你也在香港的机房里被凌晨的告警叫醒,愿这份手册能让你十分钟内做出正确的 Page File 决策,把“内存溢出”的枪口,拧回到可控的位置。