带您理解SQLSERVER是如何执行一个查询的

带您理解SQLSERVER是如何执行一个查询的

看这篇文章以前,阁下能够先看一下下面的文章html

SQLSERVER独特的任务调度算法"SQLOS"程序员

SQL Server SQLOS 的任务调度[转]web

 

翻译自:算法

http://rusanu.com/2013/08/01/understanding-how-sql-server-executes-a-query/sql

http://www.codeproject.com/Articles/630346/Understanding-how-SQL-Server-executes-a-query数据库

不知道是哪篇文章抄哪篇文章的 ,无论他了,我也偷他们的文章,嘎嘎嘎嘎嘎。。。缓存

我将会用尽本人的全部功力而且结合研究SQLSERVER以来的知识去翻译这篇文章服务器

但愿你们多多支持o(∩_∩)o,其实我也是站在巨人的肩膀上的,呵呵~网络

特别说明:为了节省篇幅,文中会对原文有删减,删减的部分都是一些不重要的部分session

在这里感谢有道词典(我不是卖广告啊!!!) 

 

正式开始


链接方式和请求

若是你是一个开发者,而且你的程序使用SQLSERVER来作数据库的话

你会想知道当你用你的程序执行一个查询的时候实际发生了什么事情

我但愿这篇文章可以帮你写出更好的数据库应用程序和帮你更深刻了解遇到的数据库性能问题

 

SQLSERVER是一个C/S模型的平台。惟一和数据库交互的方式只有发送包含数据库命令的请求到数据库服务器端。

客户端和数据库通讯的协议使用一种叫作TDS的协议(Tabular Data Sream)

园子里的文章:

表格数据流协议TDS

TDS协议解析(转载)

若是你用微软的Network Monitor工具来抓取SQL Server和客户端之间的网络包

你会看到使用的是TDS协议

在Description那一列

TDS:Response,Version=7.1......

TDS:SQLBatch,Version=7.1.......

那四个SSL链接是客户端登陆SQLSERVER前作的加密链接(这里无论你有没有用SSL加密数据传输,SQLSERVER都会在登陆前加密

用户发过来的用户名和密码,而登陆了以后才使用您配置的SSL证书来加密客户端和SQLSERVER往来的数据)

SQLSERVER都会加密客户端发过来的用户名和密码(使用SQL验证不是使用Windows验证)

你们能够留意一下SQL ERRORLOG里在SQLSERVER启动的时候的日志

会看到一句:A self-generated certificate was sccessfully loaded for  encryption

默认状况下SQL Server会自动生成一个证书并使用这个证书来对客户端登陆SQLSERVER的时候的链接作SSL加密
登陆了SQLSERVER以后,就不会对链接/所传输的数据作加密了

并且SQL Server自动生成的证书。每次SQL Server启动时,它自动生成的证书都是不同的

 

MSDN是这样描述的:Tabular Data Stream协议,应用程序可以使用下面的几种已经实现了TDS协议的驱动程序里的其中一种

驱动程序来链接数据库,包括:

the CLR managed SqlClient

OleDB

ODBC

JDBC

PHP Driver for SQL Server

开源的 FreeTDS 实现

当你的应用程序命令数据库如何去作的时候会经过TDS协议向数据库发送一个请求

发送的请求自己能携带下面几种格式的信息

(1)批处理请求

这种请求类型只会包含一个须要执行的批处理TSQL文本。这种类型的请求不能带有参数,不过,TSQL批处理脚本里

能包含本地变量的定义。这种类型的请求通常都是使用SQLCLIENT驱动程序发送的,

当你使用SqlCommand 对象调用下面语句的任何一个的时候,而且没有传入任何参数

 SqlCommand.ExecuteReader()

 SqlCommand.ExecuteNonQuery()

 SqlCommand.ExecuteScalar()

 SqlCommand.ExecuteXmlReader()

当你用SQL PROFILER监视你会看到一个:SQL:BatchStarting 事件类型

 

(2)远程过程调用请求

这个请求类型包含带有若干个参数的存储过程。

当你用SQL PROFILER监视你会看到一个:RPC:Starting 事件类型

 

(3) Bulk Load大容量装载请求

 大容量装载请求是一种特别的使用bulk insert操做符的请求,

例如使用

BCP.EXE工具(咱们常说的BCP命令)

bulk insert语句

IRowsetFastLoad OleDB 接口

C#里面的SqlBulkcopy类

大容量装载请求跟其余类型的请求是不一样的,

由于请求经过TDS协议传送到SQLSERVER的时候,还未传送完毕,SQLSERVER就开始执行请求所要作的操做了

(通常来讲,整个请求的数据包所有发送到SQLSERVER那里,SQLSERVER认为是完整的数据包才开始执行请求)

可是大容量装载请求不同,数据包里包含有大量的数据,这些数据是附着在请求里的,若是要把整个请求传送完毕

SQLSERVER才开始执行请求,那不知道要等到何年何月了???

这样容许SQLSERVER开始执行请求,而且开始消费掉数据流中所插入的数据

 

 

下面是比较久之前的一张图片,你们能够参考一下,图片内容对于如今的SQLSERVER不必定正确

链接模块(模块化)

 


任务(Tasks)和工做者(Workers)

在一个完整的TDS请求到达SQLSERVER数据库引擎的时候,SQLSERVER会建立一个任务(task)去处理请求

想查询当前SQLSERVER里面的全部请求,可使用sys.dm_exec_requests  这个DMV视图

 

任务(Tasks)

上面提到的任务会被建立用来处理请求,一直到请求处理完毕为止。

例如:若是请求是一个批处理请求类型的请求,任务(Tasks)会执行整个SQL批处理,不会只负责执行SQL批处理里的单独一条SQL语句

在SQL批处理里的单独的一条SQL语句不会建立一个新的任务(Tasks)。

固然,在SQL批处理里的单独的一条SQL语句有可能会并行执行(一般使用MAXDOP,或Degree Of Parallelism)

在这种状况下,任务(Tasks)会再生新的子任务(sub-Tasks)去并行执行这个单独的SQL语句。

若是请求返回了批处理所要的完整的结果集,而且结果集已经被客户端从SQLSERVER的结果集缓存里取走

而且你在C#代码里dispose  了SqlDataReader,你会在sys.dm_os_tasks这个DMV视图里看到你的请求

所用的任务(Tasks)。

当一个新的请求到达SQLSERVER服务器端的时候,而且这时候任务(Tasks)已经被建立出来去处理这个请求了

若是这时候任务(Tasks)处于挂起(PENDING)状态,现阶段SQLSERVER还未知道这个请求的实际内容,

那么,被建立出来的任务必须首先去执行这个请求,而且数据库引擎也要分配一个工做者(Worker)去处理这个请求

工做者(Workers)

工做者(Workers)是SQLSERVER线程池里的一些线程,一些 工做者(Workers)/工做线程在SQLSERVER

初始化的时候就被建立出来,而另外一些工做者(Workers)会根据需求而建立,当建立的数量达到max worker threads

这个配置值的时候就不能再建立了,下图显示为0,他并非说能够建立无限的工做者(Workers)

个人机器的配置是酷睿i3,双核四线程,那么,若是max worker threads配置为0

最大的工做者(Workers)数目能够达到256个

 

实际上,只有工做者(Workers)才真正执行SQL代码。

工做者(Workers)每时每刻都等待那些已经传送进去SQLSERVER的请求的任务(Tasks)

从被挂起(PENDING)状态成为能够运行的状态,每一个工做者(Workers)只会处理和执行一个任务(Tasks)

这时候,工做者(Workers)会一直处于工做状态,并一直被占用,直到他的工做完成为止(task finishes)

若是当前没有可用的工做者(Workers)供给正在处于挂起状态的任务(Tasks)使用的话,那么这个任务(Tasks)

只能一直等待直到那些已经在执行/运行的任务(Tasks)执行完毕,另外,工做者(Workers)在处理完一个

任务(Tasks)以后也会继续处理下一个处于挂起状态的任务(Tasks)。

对于一个SQL批处理请求,工做者(Workers)会处理那个携带着那个SQL批处理的任务(Tasks)

而且会执行SQL批处理里面的每条SQL语句。

 

有人就会问了:一个SQL批处理里的SQL语句不就是并行执行吗?

(=> 请求request =>任务 task =>工做者 worker),一个批处理请求进来,多个工做者去处理这个批处理请求里的每条SQL语句,

这显然就是SQLSERVER的并发处理SQL语句嘛

 

不少人都会有这个想法,其实是错误的,实际上这些SQL语句也是串行执行的,这些SQL语句的执行只能由

一个单独的线程(工做者 worker)来执行,线程(工做者 worker)在执行完一个SQL语句以后才能执行下一个SQL语句,

当SQL批处理内部的SQL语句使用了并行提示MAXDOP>1来执行SQL语句 ,这会形成建立子任务(sub-tasks),

每一个子任务(sub-tasks)也是经过上面所说的那个循环去执行的:任务建立出来以后会处于挂起状态,

其余的(工做者 worker)必须去处理这个子任务(sub-tasks)

你会在sys.dm_os_workers这个DMV视图里看到SQLSERVER当前的工做者 worker列表和他们的当前状态

 


解释(Parsing)和编译(Compilation)

一旦一个任务(task)开始执行一个请求,第一件要作的事情就是:去理解请求里面的内容

在这一步,SQLSERVER的行为更像一个代码解释的虚拟机(相似于JVM):在请求(request)里面的TSQL代码将会被逐一解释

而且会生成一棵抽象语法树去处理这个请求。整个批处理请求会被解释和编译,若是在这一步发生错误,

SQLSERVER会给出编译/解释错误的提示,这个请求也会被终止不会执行,任务(task)和工做者(worker)都会被释放,

释放出来的工做者(worker)会继续处理下一个被挂起的任务(task)。

SQL语言和TSQL(SQLSERVER里叫TSQL,ORACLE里叫PLSQL)语言是一种高等的描述性语言

当一个SQL语句很复杂的时候,试想一下,一个SELECT 语句伴随着多个JOIN

 1 USE [GPOSDB]
 2 GO
 3 SELECT * FROM [dbo].[CT_Append] AS a
 4 INNER JOIN 
 5 [dbo].[CT_FuelingData] AS b 
 6 ON a.[VC_A_CardNO]=b.[VC_FD_Cardno]
 7 INNER JOIN
 8 [dbo].[CT_Dis_FuelingData] AS d
 9 ON a.[VC_A_CardNO]=d.[VC_FD_Cardno]
10 INNER JOIN 
11 [dbo].[CT_InhouseCard] AS e
12 ON e.[VC_IC_CardNO]=d.[VC_FD_Cardno]
13 INNER JOIN
14 [dbo].[CT_OuterCard] AS f
15 ON f.[VC_OC_CardNO]=a.[VC_A_CardNO]


编译好的TSQL批处理不会产生可执行代码(executable code,相似可执行的二进制的exe文件),

这里更像本地CPU指令,甚至于相似C#的CLI指令或者JAVA的JVM bytecode

不过,这里会产生用于访问表数据的执行计划(query plans),这些执行计划描述了如何去访问表和索引,

如何去搜索和定位表里面的行数据,如何根据SQL批处理里的SQL语句去作数据操做。

例如:一个执行计划会描述一种数据访问路径-》访问在t表上的索引idx1,定位到关键字为‘k’的那行记录,

最后返回a列和b列这两列数据。

 

另外:开发者一般都会犯一个广泛的错误

在一个TSQL语句里写不少的条件选择,一般这些条件选择都会用在带有OR 的where子句里

例如:cola=@parameter OR @parameter IS NULL

对于开发者必定要避免这种状况。

这个时候,编译必定要得出一种通用的执行计划,不管任何参数代入到这个执行计划里都能得出最优的结果

在TSQL里的参数化(Dynamic Search Conditions)

例以下面SQL语句:

1 SET STATISTICS PROFILE ON
2 GO
3 INSERT INTO [dbo].[SystemPara] ( [ParaValue], [Name], [Description] )
4 VALUES  ( '2', -- ParaValue - varchar(50)
5           '3', -- Name - varchar(50)
6           '4'  -- Description - varchar(50)
7           )

当你打开SET STATISTICS PROFILE ON开关的时候,你会在Argument列和DefinedValues列看到

 SQLSERVER会将输入的值2,3,4赋值到Expr1004,Expr1005,Expr1006这三个变量里

并作一些类型转换,Expr1004=CONVERT_IMPLICIT(VARCHAR(50),[@1],0)

2这个值会代入都@1变量里,而后经过类型转换赋值给Expr1004

recordno这一列也是,经过getidentity((277576027),(14),null)函数得到自增值

而后赋值给Expr1003

 

那么,在SQLSERVER的执行计划里,你们能够想象成以下样子

1 INSERT INTO [dbo].[SystemPara] ([RecordNo], [ParaValue], [Name], [Description] )
2 VALUES(Expr1003,Expr1004,Expr1005,Expr1006)

将实际的值先赋值给@1,@2,@3,@4 再经过类型转换赋值给Expr1003,Expr1004,Expr1005,Expr1006

Expr1003=类型转换(@1)

Expr1004=类型转换(@2)

Expr1005=类型转换(@3)

Expr1006=类型转换(@4)

为什麽SQLSERVER不直接使用下面的执行计划呢?

1 INSERT INTO [dbo].[SystemPara] ([RecordNo], [ParaValue], [Name], [Description] )
2 VALUES(1,2,3,4)

还要类型转换,参数代入这麽麻烦,SQLSERVER不是有病吗???

这里涉及到执行计划重用,若是使用上面的执行计划,编译的时间是很快,可是

若是我插入的值是:9,8,6,7

1 INSERT INTO [dbo].[SystemPara] ([RecordNo], [ParaValue], [Name], [Description] )
2 VALUES(9,8,6,7)

SQLSERVER不能重用上次的执行计划,又要从新生成执行计划,您说这样的效率。。。。。。。。

 


优化(Optimization)

刚才说到选择一种数据访问路径(执行计划),如今继续说一个请求(request)的生命周期的下一步:优化

在SQLSERVER里面,优化意味着从多个选择条件中选择最佳的数据访问路径。

考虑一下,若是你有一个简单的涉及到两个表的join查询,每一个表都有额外的索引,

这里就有4种可选的执行方案,去访问表中的数据

由于有这麽多的可选方案,查询复杂度已经比较高了,若是这时候表中的索引继续增多的话,查询复杂度有可能以指数的方式增加

再加上JOIN联接原本就有三种联接方式:nested loops join、merge join、hash join

可想而知,优化这个名词在SQLSERVER里是多么重要,SQLSERVER使用一个查询优化器来预估这中间要消耗的时间,IO,CPU

查询优化器会考虑各类执行方案,SQLSERVER会尽力基于每种执行方案的开销去做出评估,而后尽量选择一个开销最低的

执行方案。SQLSERVER首先会计算在现有的表数据量下各类执行方案各自须要多少的开销。为了选出一个开销最低的执行方案,

SQLSERVER须要知道作联接的每张表的数据量和表里面各个字段的数据的分布,这就须要靠统计信息

由于统计信息原本就是用来统计这些数据的。另一个要考虑的因素就是,每种执行方案所须要的CPU消耗和内存消耗

综合以上各类因素,SQLSRVER会在每种执行方案里算出一个cost值

SQLSERVER会在这些执行方案里选出一个cost值最低的执行方案做为执行计划执行

 

你们看一下,SQLSERVER要对上面各类因素进行考虑,这里考虑是须要时间的,因此为什麽SQLSERVER

须要将执行计划缓存到内存里以便未来继续使用这个执行计划,就是为了节省编译时间

未来一样的请求进入到SQLSERVER,而且这些请求可以在CACHE里找到一个已经编译了和优化了的执行计划

他们就能跳过查询优化器的优化阶段

 

这里必定要注意:一样的请求进来SQLSERVER的时候,不管CACHE里有没有能够重用的执行计划,SQLSERVER都须要

对请求里的SQL语句进行解析,因此我上面才说:就是为了节省编译时间  而不是  就是为了节省解析/编译时间

 

解释和编译模块(模块化)


执行(Execution)

一旦查询优化器选择了一个执行计划,请求(request)就能够开始执行了。执行计划会被翻译成为一棵实际的执行树

每一个树节点都是一个操做符,全部操做符都会实现一个有3个方法的抽象接口,分别是open(), next(), close()

若是阁下是C#程序员或者是JAVA程序员,必定不难理解什么是接口,什么是方法,什么是抽象接口

MSDN里有相关的资料:Showplan 逻辑运算符和物理运算符参考

查询计划是由物理运算符组成的一个树(执行树)


逻辑运算符
逻辑运算符描述了用于处理语句的关系代数操做。 换言之,逻辑运算符从概念上描述了须要执行哪些操做。


物理运算符
物理运算符实施由逻辑运算符描述的操做。 每一个物理运算符都是一个执行某项操做的对象或例程。 例如,某些物理运算符可访问表、索引或视图中的列或行。 其余物理运算符执行其余操做,如计算、聚合、数据完整性检查或联接。 物理运算符具备与其关联的开销。
物理运算符初始化、收集数据,而后关闭。 具体来说,物理运算符能够响应下列三种方法调用:
Init():Init() 方法使物理运算符初始化自身并设置全部须要的数据结构。 尽管一个物理运算符一般只接收一次 Init() 调用,但也能够接收许屡次调用。
GetNext():GetNext() 方法使物理运算符得到数据的第一行或后续行。 物理运算符能够不接收 GetNext() 调用,也能够接收许屡次调用。
Close():Close() 方法使物理运算符执行某些清除操做,而后关闭。 一个物理运算符只接收一个 Close() 调用。
GetNext() 方法返回一个数据行,它的调用次数做为 ActualRows 显示在使用 SET STATISTICS PROFILE ON 或 SET STATISTICS XML ON 生成的显示计划输出中。 有关这些 SET 选项的详细信息,请参阅 SET STATISTICS PROFILE (Transact-SQL) 和 SET STATISTICS XML (Transact-SQL)。

 

文中说的操做符实际上指的就是物理运算符:三个方法指的是open()=init(),next()=getnext(),close()=close()

每一个物理运算符就是调用本身的三个方法

 

在SQLSERVER执行请求的过程当中,执行树的根节点会不断循环的调用open(),而后重复调用next()直到返回false值

最后调用close()。树的根节点的运算符会依次调用他的子节点的一样的运算符,而子节点又会依次调用他的子节点的一样的运算符

一直调用下去。在树的叶子节点通常都会是读取表数据或表索引的物理运算符。而执行树的中间节点通常都是一些实现不一样数据操做的运算符

例如:过滤表数据、join链接、对数据排序。那些使用并行的查询会使用一个特别的运算符叫作:Exchange Oprators(交换操做)

 交换操做运算符在执行的过程当中会使用多线程(tasks => workers),调用每一个线程去执行子树的执行计划,

而后聚合这些运算符的输出结果,在这个过程当中会使用典型的(多生产者《-》一个消费者模式)。

关于Exchange Oprators(交换操做) 能够参考这篇文章:SQL Server 2000中的并行处理和执行计划中的位图运算符

咱们使用 SET STATISTICS PROFILE ON 就能够看到执行树,下面是一些列的名称,更详细的就不说了,网上有不少资料

NodeId:树节点

Parent:父节点

PhysicalOp:物理运算符

LogicalOp:逻辑运算符

 

这种执行树的执行模型不单只应用于查询,插入,删除,更新的执行都是一样利用执行树来执行的

插入记录、删除记录、更新记录都会有相应的运算符

 

一个执行树没有子树的状况

一个执行树具备子树的状况

 

若是执行树具备子树,他的执行方式也是从子树的叶子节点开始执行,一直执行到树的根节点

 

特别要介绍一下,这些运算符也有中止-继续的行为特性,意思是说除非他们的子节点运算符已经吸取完全部的输入,他们才能产生输入

例如:排序运算符,排序运算符在最初调用next()函数的时候不会返回任何结果由于这时候他的子节点尚未读取完全部数据,

这时候须要中止执行next()函数(每一个运算符建立出来就会不停调用next函数),直到他的子节点读取完全部数据他才能对这些数据

进行排序(继续调用next()函数),取出结果集并排序

 

若是数据已经缓存在内存里了,SQLSERVER就不须要去磁盘里取数据,直接在内存里取数据,内存里的这块空间,

SQLSERVER官方术语叫:Buffer pool

而在内存里缓存执行计划的这块空间,SQLSERVER官方术语叫:Plan Cache

 

 

 执行模块(模块化)

 


结果(Results)

在执行完毕以后,SQLERVER会将结果集返回给客户端应用程序

当执行到执行树的根节点的时候,根节点一般负责将结果集写入到网络缓冲区(network buffers)

而后将这些结果集发送回客户端。一个完整的结果集尚未建立完毕,一部分的结果首先会存放到中间存储(内存或磁盘)

而后逐段逐段发送给客户端,例如一个SQL语句查询的结果须要返回10条记录,有3条记录已经生成好了,能够返回给客户端了

SQLSERVER首先将这3条记录放入中间存储(内存或磁盘),也能够叫网络缓冲区,等客户端来取走这3条记录,如此类推。

返回结果集给客户端的时候,SQLSERVER用的是网络流控制协议。

若是客户端没有积极地将这些结果集取走(例如调用SqlDataReader.Read())。最终会致使网络流控制组件不得不阻塞

结果集发送端而且会挂起查询的执行。

只有网络流控制组件协调和缓解了网络资源的需求(网络没有阻塞),查询才会恢复,而且继续生成结果集

不知道你们有没有遇到过等待类型:ASYNC_NETWORK_IO的等待

 

上图里,客户端二就要等待,在SQLSRVER里查询就会显示ASYNC_NETWORK_IO类型的等待

 

 

有趣的是,OUTPUT参数的返回,OUTPUT参数的值会被插入到返回给客户端的结果集的网络数据流中。

当请求完成的时候,OUTPUT参数值只能在查询执行的最后写到结果集中,这就是为什麽OUTPUT参数值

只有当全部的结果集都返回了才能检查OUTPUT参数的值


查询执行过程当中要赋予的内存(Query Execution Memory Grant)

一些运算符须要固定的内存去执行他们的工做。排序运算符为了进行排序须要内存去存储输入到排序运算符的数据

Hash join和hash聚合必须创建大型的hash表去执行他们的工做。执行计划知道那些未完成的运算符须要多少内存

根据运算符类型,预估的行记录,运算符必需要处理统计信息提供给他的表中的字段的大小。

那些在执行计划里的运算符所须要的总的内存咱们一般称为内存赋予

 

试想一下,当很是多的并发查询被执行的时候,由于大量的昂贵的运算符(这些运算符通常都须要不少内存,因此称之为昂贵的)

须要请求内存,在同一时间里面他们可以用尽计算机的内存。

 

为了阻止这种状况的发生,SQLSERVER使用一种叫“资源信号量”的东西。这个东西可以确保正在执行的查询的总内存分配不会超过

当前计算机中的内存总和。当总的内存分配就快耗尽当前服务器里的可用内存的时候,正在执行的查询必需要等待那些就快执行完毕

的查询去释放他们拥有的内存。

您能够查询sys.dm_exec_query_memory_grants这个DMV视图来获取当前的内存分配(请求的内存,分配了的内存)

当一个查询必需要等待内存的赋予/分配,在SQL PROFILER里能够看到Execution Warnings 事件类型

 

Execution Warnings 事件类型指出了当SQL语句或者存储过程执行的过程当中的内存分配警告

这个事件类型可以监视必需要等待一秒或更多内存的某些查询,或者获取内存失败的查询

 

在SQL PROFILER里,一些与内存有关的事件类型

Exchange Spill 事件类型

Sort Warnings 事件类型:排序的时候所需内存不足

Hash Warning 事件类型

 

相关语句

1 select * from sys.dm_exec_query_resource_semaphores
2 
3 
4 select * from sys.dm_exec_query_memory_grants

 1 SELECT [session_id],
 2 [request_id],
 3 [start_time],
 4 [status],
 5 [command],
 6 [wait_type],
 7 [text_size],
 8 [language] ,
 9 [transaction_isolation_level],
10 [row_count],
11 [granted_query_memory],
12 [executing_managed_code]
13 FROM sys.[dm_exec_requests]

 


我如何利用这些信息(How can I use all this information)

上面的信息有可能帮您解决performance troubleshooting problems(性能问题)

一旦您明白了您的客户端正在发送多个请求到SQLSERVER,SQLSERVER端正在建立多个任务(task)去处理

您发给他的请求,性能的谜题就能够很简单地解决了:不少时候,您的任务不是正在执行(正在占领CPU)就是处于正在等待

每次等待,SQLSERVER都会依靠内部等待统计信息去收集等待的信息(等待什么和等了多久)。

利用收集回来的统计信息去解决性能瓶颈是很是好的方法


附上两张完整的图


总结

文中好像遗漏了Scheduler

Scheduler

对于每一个逻辑CPU,SQLSERVER会有一个scheduler与之对应,在SQL层面上表明CPU对象,

只有拿到scheduler全部权的worker才能在这个逻辑CPU上运行

 

 

翻译完结了~

若是对阁下有帮助的话,但愿给个推荐吧o(∩_∩)o

相关文章:

SQL Server 性能问题—等待RESOURCE_SEMAPHORE

如何知道TSQL语句已经运行了多久

SQL Server 链接加密 (1) -- SQL Server connection encyption

 

若有不对的地方,欢迎你们拍砖o(∩_∩)o

-----------------------------------------------------------------------------------------

2013-10-26 补充

关于时间统计

1 SET STATISTICS TIME ON 
2 GO
1 SQL Server 分析和编译时间: 
2    CPU 时间 = 0 毫秒,占用时间 = 58 毫秒。
3 
4 SQL Server 执行时间:
5    CPU 时间 = 0 毫秒,占用时间 = 1 毫秒。

我以为SQLSERVER显示出来的数据统计数据应该就是在分析和编译模块和执行模块的开头和结尾插入时间统计代码

来统计出所使用的时间的


关于时间统计范围

图中红色圆圈部分我认为是SQLSERVER团队插入时间统计代码的地方

分析和编译时间:不管有没有plan cache,从进入命令分析器开始,到离开查询优化器结束

执行时间:从查询执行器开始,都离开查询执行器结束

有人会以为应该是结果集存放在网络缓冲区或者结果集真正到客户端的手里才算是执行时间的结束

-------------------------------------------------------------------------

可是我不这麽认为,到达网络缓冲区以前结果集已经生成好了,表示查询执行完毕了

查询执行完毕的意思:全部结果都已经生成好了,不是说客户要10条记录先生成好3条记录,将这3条记录先放入网络缓冲区待客户端取走

这样统计是不科学的,应该是10条记录都已经生成好了(为标准),而且在传送到网络缓冲区以前

 

客户端有没有取走,结果集何时到达客户端,SQLSERVER并不须要关心,由于各类的状况,例如:网络阻塞

这个不能算在SQLSERVER的执行时间上

相关文章
相关标签/搜索