背景介绍】
通常SQL Server在运行时,我们在错误日志里,会发现有多少个CPU被检测到。如下面的例子,SQL Server检测到了总共有4个CPU.
我们知道SQL Server所能检测到的CPU是逻辑CPU个数。而不是物理CPU个数。双核CPU对于SQL Server来讲,是两个逻辑CPU,如果有超线程的话,CPU个数还要翻一倍。逻辑CPU个数,通常能在Windows 的Task Manager里显示出来, 在CPU Usage History里,有多少个框,就有多少个逻辑CPU
【问题】
但是在有一些很高端的机器上,我们会发现一些比较有趣的现象。SQL Server所能检测到的CPU个数,竟然和逻辑CPU个数不符合。
如下面的机器配置,有4个物理CPU, 每个CPU上有10个Cores, 而且也enable了超线程。所以这台机器上,总共有80个逻辑CPU,按道理,SQL Server应该能检测到80个CPU才对。
但我们查看SQL Server的错误日志,SQL Server 竟然只能检测到40个CPU.
在另外一台相同配置的机器上,SQL Server 重启后,有时候能检测到20个CPU, 有时候能检测到60个CPU。
SQL Server能检测到的CPU个数竟然会随着SQL Server重启,而有所变化。本来SQL Server性能很好,重启后,SQL Server能使用的CPU变成20了,性能就会变得很差。这对于很高端的应用来讲,是不能接受的。
【调查】
对于这个非常奇怪的现象,我们似乎是无从下手,难道是SQL Server的版本限制?但是我们的SQL Server版本已经是Enterprise了, 好像是SQL Server的一个Bug, 但是又不能确定。
调查一
我们做了进一步的研究。我们知道,SQL Server所检测到的CPU个数,是通过调用下面的函数所得到的:
SYSTEM_INFO siSysInfo;
GetSystemInfo(&siSysInfo);
printf(" Number of processors: %u\n", siSysInfo.dwNumberOfProcessors);
我们写了一个repro程序,完整的代码如下:
// SystemInfo.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "user32.lib")
int _tmain(int argc, _TCHAR* argv[])
{
SYSTEM_INFO siSysInfo;
// Copy the hardware information to the SYSTEM_INFO structure.
GetSystemInfo(&siSysInfo);
// Display the contents of the SYSTEM_INFO structure.
printf("Hardware information: \n");
printf(" OEM ID: %u\n", siSysInfo.dwOemId);
printf(" Number of processors: %u\n", siSysInfo.dwNumberOfProcessors);
return 0;
}
编译执行上述代码,问题能够重现:
D:\TEMP>systeminfo_x64.exe
Hardware information:
OEM ID: 9
Number of processors: 60
D:\TEMP>systeminfo_x64.exe
Hardware information:
OEM ID: 9
Number of processors: 20
D:\TEMP>systeminfo_x64.exe
Hardware information:
OEM ID: 9
Number of processors: 20
D:\TEMP>systeminfo_x64.exe
Hardware information:
OEM ID: 9
Number of processors: 60
所以,到了这一步,我们可以判定,问题跟SQL Server无关。但是为什么GetSystemInfo() 得到的CPU个数会变来变去呢?难道是GetSystemInfo() 这个API的Bug?
调查二
我们仔细研究了GetSystemInfo这个API, 其中,_SYSTEM_INFO 这个结构描述在:
http://msdn.microsoft.com/en-us/library/ms724958(v=vs.85).aspx 对dwNumberOfProcessors的描述如下:
dwNumberOfProcessors: The number of logical processors in the current group
这里,”current group” 这个词引起了我们的注意。
调查三
我们做了进一步研究,发现在Windows 7和Windows Server 2008 R2 里,我们引入了Processor Group 这个概念, 具体描述在http://msdn.microsoft.com/en-us/library/dd405503(v=vs.85).aspx 简单的讲,64位的Windows7和Windows Server 2008 R2为了能够在一台机器上,支持超过64个逻辑CPU, 引入了Processor Group这个概念。Processor Group会把一些逻辑CPU编成一个组,但是一个组内的逻辑CPU总数不能超过64个。超过64个的话,要编入另外一个组。
所以,对于我们的系统,有80个逻辑CPU, 那么就会编成两个组。是不是每个组均匀的分配CPU呢,不是的。操作系统会在重启的时候,根据逻辑CPU之间的物理远近,自动进行编组。所以,在有的系统上,我们会看到40-40分配,在另外系统上,我们会看到20-60分配。
那么,每当一个进程运行的时候,它会在哪个Processor Group上运行呢? 操作系统会按照Round-robin方式,给每个进程分派Processor Group, 一旦一个进行分配到了一个Processor Group, 就会一直在这个Processor Group里运行,直到该进程结束为止。
【解释】
所以,这就解释了,为什么SQL Server 2008 有时候能检查到20个CPU, 有时候能检查到60个CPU, 这完全是因为Processor Group在起作用。每次SQL Server 重新启动后,有可能被分配到另外一个Processor Group, 而导致能检查到的CPU个数出现变化。
我们可能会问,为什么SQL Server 2008 会通过调用GetSystemInfo(&siSysInfo); 的办法来获取逻辑CPU个数呢?难道不能用其他API么?这是因为,Processor Group这个概念是在Windows Server 2008 才引入的。当开发SQL Server 2008的时候,并没有做相应的变化。当然,这个问题在SQL Server 2008 R2得以解决。SQL Server 2008 R2能最多同时检测到256个逻辑CPU.
【解决方案】
最好的解决方案是把SQL Server 2008 升级到SQL Server 2008 R2. 如果短期内不可行的话,我们可以把超线程禁用。这样系统上逻辑CPU的个数会变成40个。小于64个,所有的CPU会被编入一个组。虽然SQL Server能检测到的CPU只有40个,但至少系统是稳定的。
我们可能还会问,是否有办法,能否人工干预Processor Group的编组,一个组有64个CPU, 另外一个组有16个CPU? 对于NUMA架构的话,我们可以做一些适当的调整,具体可参考: http://support.microsoft.com/kb/2506384
另外,如果我们碰到Processor Group按20-60分配的情况,对系统整体性能是有影响的。建议打一个操作系统的补丁:
http://support.microsoft.com/default.aspx?scid=kb;EN-US;2510206