香港服务器上的 Windows Server 2012:我如何把分页文件调到“听话”,并把数据库 I/O 抖动按回去

凌晨 2:40,香港葵涌机房的NOC的大屏上,数据库实例的延迟每隔几分钟就炸一次——从 8ms 突刺到 300ms。客户的交易系统一顿一顿的,电话没停过。那一刻,我盯着那台跑着 Windows Server 2012 的老伙计,心里只有一句话:“得先让内存和分页文件别内耗,再谈 I/O。”
现场环境与硬件拓扑
| 组件 | 型号/版本 | 关键参数 | 备注 |
|---|---|---|---|
| 机型 | Dell PowerEdge R730 | 2×Xeon E5-2630 v3, 96GB RAM | 双电冗余 |
| 操作系统 | Windows Server 2012 Standard(非 R2) | 已打至最新补丁 | GUI 安装 |
| 数据库 | Microsoft SQL Server 2014 Standard | 单实例,OLTP | 本文也给出 MySQL 补充要点 |
| RAID 控制器 | PERC H730P | 2GB Cache,Write Back + Force WB w/o BBU 关闭 | 开启 Write Back(BBU 健康) |
| 系统盘 | 2× 300GB 10K SAS | RAID1 | C: |
| 数据盘 | 8× 600GB 10K SAS | RAID10 | D: DATADATA |
| 日志盘 | 2× Intel S3710 400GB | RAID1 | E: LOGLOG(企业级 SATA SSD) |
| TempDB | 同 E: | 固定大小文件 | |
| 分页盘 | 2× Intel S3710 200GB | RAID1 | G: PAGEPAGE 专用,不放别的东西 |
| 网络 | 双口万兆 + LACP | — | 前端接交易中间层 |
为什么不用 NVMe?
Windows Server 2012(非 R2)对 NVMe 的支持不如新系统靠谱;我们选择了企业级 SATA SSD,减少驱动“玄学”。稳定 > 一切花哨。
症状复盘:抖动并非“磁盘慢”,而是“内存-分页-存储”的连环踢
每隔 5–10 分钟,磁盘队列长度在数据盘 D: 突然拉高(> 50),平均磁盘读等待飙到 200–300ms。
SQL 等待类型短时以 PAGEIOLATCH_* 和 WRITELOG 为主。
Windows Perfmon 里:
- Memory\Committed Bytes 接近 Memory\Commit Limit;
- Paging File(_Total)\% Usage 突破 35%,Memory\Page Reads/sec 有刺刀式上扬;
- Process(sqlservr)\Working Set 被动回落,紧接着发生磁盘抖动。
资源监视器显示零星硬错误(Hard Faults/sec)在抖动点飙高。
初步判断:系统托管(System Managed)的分页文件跨了 C: 和 D:,在数据库读写压力点触发增长/收缩与争抢;同时 SQL Server 的 max server memory 太激进,留给 OS、驱动和文件缓存的余量不足,触发内存修剪,连带换入换出,在 D: 上与数据文件抢 I/O。
诊断方法(我在现场怎么一步步定位)
1)看“承诺内存/提交限制”(Commit)是否顶天花板
# 快速看 Commit 与 Pagefile 使用
Get-Counter '\Memory\Committed Bytes','\Memory\Commit Limit','\Paging File(_Total)\% Usage','\Memory\Page Reads/sec' -SampleInterval 1 -MaxSamples 10
经验阈值:Committed Bytes / Commit Limit 长期 > 0.8 就要警觉;Page Reads/sec 成团攀升通常意味着真的在抖。
2)确认分页文件的真实布局与策略
# 是否系统托管分页
(Get-WmiObject -Class Win32_ComputerSystem).AutomaticManagedPagefile
# 现有分页设置
Get-WmiObject -Class Win32_PageFileSetting | Select-Object Name, InitialSize, MaximumSize
现场发现:系统托管且分布在 C: 和 D:,D: 正是数据盘。
3)验证卷格式与分配单元
# 看簇大小
fsutil fsinfo ntfsinfo D:
fsutil fsinfo ntfsinfo E:
D: 被人用默认 4KB 格式化过,E: 为 64KB。数据库卷建议 64KB。
4)SQL 侧等待与内存
-- 观察等待类型
SELECT TOP 10 * FROM sys.dm_os_wait_stats ORDER BY wait_time_ms DESC;
-- 内存压力
SELECT * FROM sys.dm_os_process_memory;
5)控制器缓存与电池
进入 PERC BIOS 检查:BBU 正常,Write Back 打开;若 BBU 异常导致 Write Through,会放大抖动。
解决思路:三板斧把“内存-分页-I/O”链条重新梳直
把分页文件固定到一块独立 SSD(G:),且固定大小,消灭动态增长与跨卷争抢。
给 OS 留足内存余量:限制 SQL Server max server memory,并启用“锁定内存”(Lock Pages in Memory),避免被 OS 随意修剪。
把存储基本功补齐:数据库卷 64K 簇、TempDB 独立 SSD、Instant File Initialization、服务器电源策略设为高性能。
实操步骤(可直接照抄执行)
A. 固定并迁移分页文件到专用 SSD(G:)
容量怎么定?
先看峰值 Committed Bytes (峰值)。我们的峰值约 78GB,物理内存 96GB。目标是Commit Limit ≥ 峰值 + 安全余量。
由于计划使用**内核内存转储(Kernel Dump)**而非完整转储,pagefile 不必 ≥ RAM。最终我给 24GB 固定值(足以覆盖峰值余量 + dump 需求),你可以按自己峰值+10~20%来定,常见区间 16–32GB。
步骤:
# 1) 关闭系统托管分页
Set-WmiInstance -Class Win32_ComputerSystem -Arguments @{ AutomaticManagedPagefile = $false }
# 2) 删除旧的分页设置(谨慎:如果在远程会话里做,建议维护窗口)
Get-WmiObject -Class Win32_PageFileSetting | Remove-WmiObject
# 3) 在 G: 创建固定分页文件,单位 MB(这里设 24576MB = 24GB)
Set-WmiInstance -Class Win32_PageFileSetting -Arguments @{
Name='G:\pagefile.sys'; InitialSize=24576; MaximumSize=24576
}
# 4) 将系统故障转储设为“内核转储”,避免要求过大的 pagefile
# 0=禁用,1=完全,2=内核,3=小内存
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\CrashControl' -Name CrashDumpEnabled -Value 2
# 5) 重启(必须)
Restart-Computer
坑 1: 不要把分页文件放在启用数据重删或压缩的卷上(Server 2012 的重删卷不支持 pagefile)。
坑 2: 远程操作时请安排维护窗口,重启前确认你还有 out-of-band 管理(iDRAC/iLO)。
验证:
Get-CimInstance -ClassName Win32_PageFileSetting | Format-Table Name,InitialSize,MaximumSize
Get-Counter '\Paging File(_Total)\% Usage','\Memory\Commit Limit','\Memory\Committed Bytes'
B. 给 OS 留内存:SQL Server Max Memory & Lock Pages in Memory
估算(经验值):
- OS、驱动、文件缓存、杀毒、备份代理等至少预留 6–10GB;
- 96GB RAM 的机器,我通常把 SQL max server memory 设在 72–80GB 起步,根据观测微调。
操作:
-- 打开高级选项
EXEC sp_configure 'show advanced options', 1; RECONFIGURE;
-- 设置上限(例如 80GB = 81920MB)
EXEC sp_configure 'max server memory (MB)', 81920; RECONFIGURE;
启用“锁定内存”(避免工作集被 OS 修剪):
- 运行 secpol.msc → 本地策略 → 用户权限分配 → 锁定内存中的页面。
- 添加运行 SQL Server 服务的账号(如 NT Service\MSSQLSERVER 或自定义域账号)。
- 重启 SQL Server 服务。
坑 3: 启用 Lock Pages 后更要控制好 max server memory,否则你可能把 OS 吃到缺氧。
C. 存储与卷的基本功
卷按 64KB 簇格式化(数据库/日志/TempDB 卷)
PowerShell(Server 2012 可用):
Format-Volume -DriveLetter D -FileSystem NTFS -NewFileSystemLabel 'DATA' -AllocationUnitSize 65536 -Force
Format-Volume -DriveLetter E -FileSystem NTFS -NewFileSystemLabel 'LOG' -AllocationUnitSize 65536 -Force
坑 4: 重格式化会清空数据,请提前迁移/备份。
TempDB 独立 SSD,固定文件 & 固定增长
-- 按 CPU 核心数设置 4~8 个数据文件,大小相等,增长固定块
USE master;
ALTER DATABASE tempdb MODIFY FILE (NAME = tempdev, SIZE = 1024MB, FILEGROWTH = 512MB);
-- 假设创建另外 3 个
ALTER DATABASE tempdb ADD FILE (NAME = tempdev2, FILENAME='E:\MSSQL\DATA\tempdb2.ndf', SIZE=1024MB, FILEGROWTH=512MB);
ALTER DATABASE tempdb ADD FILE (NAME = tempdev3, FILENAME='E:\MSSQL\DATA\tempdb3.ndf', SIZE=1024MB, FILEGROWTH=512MB);
ALTER DATABASE tempdb ADD FILE (NAME = tempdev4, FILENAME='E:\MSSQL\DATA\tempdb4.ndf', SIZE=1024MB, FILEGROWTH=512MB);
ALTER DATABASE tempdb MODIFY FILE (NAME = templog, SIZE = 512MB, FILEGROWTH = 256MB);
开启 Instant File Initialization(IFI)
本地安全策略 → 用户权限分配 → 执行卷维护任务 添加 SQL 服务账号。
这样数据文件增长不再清零,减少写放大(日志文件不受 IFI 影响)。
电源策略设为高性能
powercfg -L
powercfg -S 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c rem 一般这是“高性能”GUID
控制器缓存
PERC H730P → Write Back + 缓存策略确认,BBU 健康。
不要因为“怕断电”就长期写直通(Write Through),那是抖动放大器。
D. 压力与验证工具
DiskSpd(替代 SQL 压测,模拟随机读写)
rem 数据盘随机读写(30% 写,64K,4 线程,队列深度 32,持续 60 秒)
diskspd -c20G -t4 -r -w30 -o32 -b64K -d60 D:\diskspd_test.dat > D:\before.txt
rem 调整后复测
diskspd -c20G -t4 -r -w30 -o32 -b64K -d60 D:\diskspd_test.dat > D:\after.txt
Perfmon 收敛指标(重点看时间窗 1 小时以上)
- PhysicalDisk(*)\Avg. Disk sec/Read 常态 < 10ms
- PhysicalDisk(*)\Avg. Disk sec/Write 常态 < 5–8ms(日志盘最好 < 3–5ms)
- Paging File(_Total)\% Usage 常态 < 10%
- Memory\Committed Bytes / Commit Limit 常态 < 0.7
- SQLServer:Buffer Manager\Page life expectancy 明显回升(结合业务解读)
结果对比(来自现场导出的核心指标)
| 指标 | 调整前(峰值/常态) | 调整后(峰值/常态) | 备注 |
|---|---|---|---|
Committed Bytes / Commit Limit |
0.92 / 0.85 | 0.72 / 0.60 | 固定分页 + 下调 SQL 内存 |
Paging File % Usage |
38% / 25% | 8% / 3% | 迁到 G:,固定 24GB |
D: Avg. Disk sec/Read |
0.28s / 0.06s | 0.012s / 0.006s | 去掉分页争抢 + 64K 簇 |
E: Avg. Disk sec/Write |
0.035s / 0.010s | 0.006s / 0.003s | 日志在 SSD RAID1 |
SQL PAGEIOLATCH_* 等待 |
间歇爆发 | 零星、业务峰时可控 | TempDB 调优也有贡献 |
| 交易 P99 延迟 | 420ms | 85ms | 峰值时段对比 |
| 抖动周期 | 5–10 分钟 | 基本消失 | 偶发峰由批处理触发,可预期 |
额外细节与那些“坑”
分页文件最小值过小:系统托管喜欢“动态”,但数据库场景最怕动态增长——锁表外还有pagefile 扩容锁;固定大小是王道。
完整内存转储 vs 内核转储:完整转储要求 pagefile ≥ RAM,很多人因此把 pagefile 设成 96GB/128GB,反而拖累 I/O。除非极端问题定位,一般内核转储够用。
多分页文件:如果要多个 pagefile,只放在快盘,且不要与数据库热路径同盘。跨盘 pagefile 未必更快,反而可能触发跨盘争抢。
杀毒/备份代理:把数据库数据、日志、TempDB、pagefile 全部加到实时扫描排除。
卷重删/压缩:数据库与 pagefile 都不要上。
MySQL on Windows 补充:
- innodb_flush_log_at_trx_commit=1(默认)保证持久性,日志盘必须快;
- innodb_flush_method=O_DIRECT 减少双缓存;
- Windows 上依然要 64K 簇、日志 SSD、Temp/ibtmp 独立快速卷;
- 留内存给 OS,避免触发系统级分页压力。
我用过的“判定清单”(落地复盘用)
- AutomaticManagedPagefile=False,pagefile 仅在 G:,固定 24GB
- CrashDump Kernel
- SQL max server memory 已下调至 80GB;LPIM 开启
- D:/E: 64K 簇;TempDB 多文件、固定增长
- Instant File Initialization 已开启
- 电源计划 高性能;PERC Write Back + BBU 正常
- 杀毒/备份排除路径生效
- Perfmon 一周观测:Avg. Disk sec/*、Page Reads/sec 收敛
常用命令速记卡
# 查看簇大小/卷信息
fsutil fsinfo ntfsinfo D:
# 查看分页使用
Get-Counter '\Paging File(_Total)\% Usage','\Memory\Page Reads/sec'
# SQL 上限
sp_configure 'max server memory (MB)'
# 电源策略
powercfg -L; powercfg -S <GUID>
# 盘格式化(危险,慎用)
Format-Volume -DriveLetter D -FileSystem NTFS -AllocationUnitSize 65536 -Force
尾声:把“跌宕”变成“脉动”
第二天早上 7 点,NOC 的曲线终于变成了我想要的样子:不是一马平川的假平稳,而是贴着业务节奏的细微脉动。抖动消失,报警沉默。客户在电话那头说:“这次稳了。”
我合上笔记本,回头看那台 R730 ——它没有变快多少,但我们让它更懂得把力气用在该用的地方:分页文件不添乱,内存不内耗,I/O 不抢道。很多时候,优化并不是上更快的硬件,而是让系统中每一层各司其职。
TL;DR(给赶时间的人)
- 固定分页文件到独立 SSD,固定大小(按峰值 Commit + 安全余量,常见 16–32GB);
- 限制 SQL 内存并启用 Lock Pages in Memory,给 OS 留 6–10GB;
- 卷 64K 簇、TempDB 独立 SSD、IFI 开启、高性能电源、控制器 Write Back;
- 用 Perfmon/DiskSpd 验证,关注 Committed Bytes/Commit Limit、Page Reads/sec、Avg. Disk sec/*。
如果你也在香港机房里被 I/O 抖动“支配过”,按上面的顺序动手,先稳内存与分页,再治存储。多数时候,问题会像那晚的冷气一样,逐渐“消散”。