SqlServer为提高自身并发能力,在Windows进程调度的基础上,又开发了一套自己的任务调度机制,抽象出了以下的一般由os代为管理的功能:
- 任务调度子管理系统
- 内存管理
- 错误、异常处理机制
- 死锁侦测和解决机制
- 运行第三方代码机制
基于这样的特点,SqlServer这部分管理职能组件被称为SQLOS。SQLOS有两大核心内容——内存管理及任务调度。
一、 为什么SqlServer需要自己的任务调度算法?
Windows是一个成熟的多任务管理操作系统,能很好地支持多并发应用,为什么SqlServer还要在上面再套一层SQLOS?那就先来看看Windows的任务调度算法有什么特点,对SqlServer来说有什么不足。
1. Windows的任务调度算法特点
- 多线程抢占式操作系统。“抢占式”意味着完全由os决定哪个线程在什么时候运行。当时间片用完,Windows会打断当前正在运行的线程,强迫其让出CPU资源。
- 以线程为任务调度单位,以进程作为内存管理单位
- 早期版本的Windows线程间同步,Windows未提供轻量级同步机制
2. 这些特点对SqlServer来说有什么问题?
- 用户连接在其生命周期中,绝大部分时间都处于空闲状态,如果每个连接都占一个线程,会造成sql进程中有大量闲置线程。维护这些线程会是很大的负担,影响SqlServer的可扩展性。
- 对于繁忙的SqlServer,同一时间点会有若干线程并发运行,其数目往往大于CPU数。如果依赖于Windows进程调度,势必造成大量线程切换,开销较大,效率较低。
- Windows完全根据优先级和时间片安排和中断线程调度,不会考虑SqlServer线程当时运行状态,它的中断点不会根据SqlServer特性进行优化。事实上,它完全只把SqlServer当一个普通的应用程序。
3. SqlServer任务调度算法特点
- 要运行任务的连接才会被分配线程,处于空闲状态的连接,SqlServer会以一组数据结构表示而不占用线程资源,这可以大大降低SqlServer进程需要的线程数。
- 对于每个CPU,SqlServer内部会有一个scheduler,由这个scheduler决定在某个时间点哪个SqlServer线程去运行。所以在Windows层面,每个cpu最多只会对应一个处于运行状态的线程,这可以大大降低Windows层面的context switch。
二、 SQLOS的任务调度算法
先来看几个SQLOS的概念:
1. scheduler
对于每个逻辑CPU,SqlServer会有一个scheduler与之对应 。scheduler在SqlServer层面代表CPU对象,只有拿到scheduler所有权的worker才能在这个逻辑CPU上运行。对应视图为sys.dm_os_schedulers
scheduler的管理遵循以下规则
- 每个scheduler上最大worker数=SqlServer最大线程数 / scheduler的数目
workers per scheduler limit = max worker threads / online schedulers
- 同一时间点,只能有一个拥有scheduler的worker处于运行状态,其他都必须处于等待状态
- scheduler是一个逻辑概念,它不与物理cpu相绑定。但是如果在sp_configure设置了CPU affinity mask,scheduler就会固定在某个特定cpu上
- scheduler可以是可见的或隐藏的。SQL Server用户进程可以看到可见的scheduler ,隐藏的scheduler 仅对SQL Server内部进程可见。有一个scheduler专用于DAC(专用管理连接)。如果服务器挂起,DBA可以通过DAC连接
2. worker
每个worker与一个线程相对应,是SqlServer任务的执行单位。SqlServer不直接调度线程,而是调度worker,使得SqlServer能够控制调度任务。对应视图为sys.dm_os_workers
worker的管理遵循以下规则
- 每个worker固定代表一个线程,并与scheduler相绑定。如果scheduler固定在某个特定cpu上,worker也会r固定在该cpu上
- 每个scheduler有worker的上限值,可以根据SqlServer负载创建或释放worker
- worker会运行一个完整的task,在task完成前不会退出scheduler
- 只有在当前有新任务要运行但没有空闲的worker,并且当前scheduler上的worker数没有超过上限值时,才会创建新worker
- 如果某个worker空闲超过15分钟,scheduler可能会删除该worker及其对应的线程。当SqlServer遇到内存压力时,也会大量删除空闲的worker以减少multi-page内存开销
3. task
在worker上运行的最小任务单元,最简单的task就是一个简单的batch。
例如用户执行以下命令
select @@version
go
select getdate()
go
那么这两个batch就分别是两个task。sqlserver会先分配给第一个batch(select @@version)一个worker,将结果返回给客户端,再分配第二个batch(select getdate())一个worker。这两个worker可能不同,设置在不同scheduler上。
只要一个task开始运行,它就不会从这个worker上被移出。例如,如果一个select语句被阻塞,worker就不能继续运行,只能进入等待状态。但是这个select task不会释放它的worker让它做其他任务,所以这个worker对应的线程只能进入等待状态。
4. Yielding
SQLOS的任务调度算法的核心,就是所有在逻辑scheduler上运行的worker都是非抢占式的(non-preemptive)。worker会始终在scheduler上运行直至结束,或者主动将scheduler让出给其他worker为止。这个让出的动作,称为Yielding(屈服; 让步)
每个scheduler都会有一个runnable列表,所有等待CPU运行的worker都会在这个列表中排队,以先进先出的算法等待sqlserver分配给它scheduler运行。
通常会在以下时间点进行Yielding:
- worker每在scheduler上运行超过4ms
- 每做64KB的结果集的排序
- 客户端不能及时把结果集取走
- 一个batch中里的每句话做完,都会做一次Yielding
5. SQLOS任务调度算法简介
以下是对上图的解释:
1)每个物理CPU有一或多个对应的逻辑CPU,每个逻辑CPU只能有一个对应的物理CPU
2)每个scheduler会被分配给一个逻辑CPU,但是逻辑CPU上可以有多个scheduler
3)每个scheduler会有多个worker,对应于每个线程
4)在客户端发来请求后,sqlserver会将其拆分为一或多个task
5)scheduler 维护两个结构:Runnable queue 和Wait list
- Runnable Queue用于维护Runnable tasks集合。由于队列先进先出,队列中第一个等待CPU时间的task将首先执行,然后是第二个task。
- Wait List是一组等待某些资源的无序task集合。在task等待的资源可用之前,等待列表中的task将一直等待;一旦资源可用,task将被推送到Runnable Queue。
6)在给定的时间点,task可以处于Runnable、Suspended或Running状态。
它可能以下面的方式改变状态
Running >> Suspended >> Runnable >> Running
当task运行时(Running状态),它可能需要某种资源(数据页,内存,锁等)。如果资源不可用,则task将变为Suspended状态,直到等到资源后状态才会改变。一旦资源可用,task将被移动到Runnable状态,这意味着该task已准备好进行处理,并在队列中等待处理器时间。 一旦执行队列中它前面的task执行完,它就会被执行。
当task正在运行时,它可能直接进入Runnable状态而不进入Suspended状态,如果task时间片(sqlserver中是4ms)耗尽又不需等待其他资源,就会发生这种情况。
Running >> Runnable >> Running