理解SQL Server是如何执行查询的 (1/3)

查询执行的总图:

根据总图的流程,详细说明每个部分:

1. 请求(Request)

SQL Server是C/S架构的平台。与它交互的唯一方式就是发送包含数据库命令的请求。应用程序和数据库之前的通信协议叫做TDS(Tabular Data Stream)协议。应用程序可以使用以下几种实现了TDS协议的客户端:

  • The CLR managed SqlClient,
  • OleDB,
  • ODBC,
  • JDBC,
  • PHP Driver for SQL Server
  • 开源的FreeTDS

TDS的请求分为以下几类:

  • 批请求(Batch Request)

这种请求只包括T-SQL文本,不包含参数,但是可以包含本地变量。在SqlClient中带有空参数列表的SqlCommand对象上执行SqlCommand.ExecuteReader(), ExecuteNonQuery(), ExecuteScalar()或者ExecuteXmlReader(),就会是批请求。通过Profile会观察到SQL:BatchStarting事件。

它包含过程标识符(Procedure Identifier,用于)和任意数理的参数。不同的过程标识符代表了不同的系统存储过程。执行带有非空参数列表的SqlCommand对象时,就是这种请求类型。通过Profile可以观察到RPC:Starting事件。

  • 批量加载请求(Bulk Load Request)

批量加载是批量插入操作所使用的一种特别的请求类型。例如BCP工具、OleDB的IRowsetFastLoad接口和SqlBulkcopy类。它是唯一一种在TDS协议中不需要完成包发送就可以开始执行的请求。开始执行后,就可以使用数据流中的数据进行插入操作了。

2. 任务(Task)

当完整的请求到达数据库引擎,SQL Server会创建一个Task去处理此请求。可以通过sys.dm_exec_requests观察请求情况。一个任务代表一个完整的请求,而不会是请求的一部分语句。同样,对于请求中的部分语句,也不会创建新的任务。有些请求的中语句会并行执行,而Task会生成sub-task处理并行。当客户端取走所有请求返回的结果集中的数据后,Task就完成了。

可以通过sys.dm_os_tasks观察Task情况。

3. 工作进程(Workers)

根据新请求所创建的Task,初始状态是PENDING。这个阶段,SQL Server并不知道请求的内容。Task须要执行这个请求,引擎就会分配worker去执行。(是分配,不是创建)

Workers是SQL Server的线程池。在SQL Server启动过程中会初始化一定数量的Workers。可以通过Max_Worker_Threads参数,按需要配置最大线程数。只有Worker才执行代码。当没有空闲的worker时,task变成Pending状态。worker完成task后,变成可用状态,才会去选择Pending状态的task执行。

在SQL批请求中,worker选择task后,执行批请求中的每一个语句。很明显,批请求中语句,是串行执行的。前一个完成,才会开始下一个。批中的某些语句会并行执行,这个并行是Task创建sub-task来完成的。而每一个sub-task会经历和task一样处理过程(如等待可用的worker来选取它并执行)。

可以通过sys.dm_os_workers查看worker及其状态。

4. 语法解析和编译(Parsing &  Compilation)

当task开始执行,首先它要弄明白请求的具体的内容。这个阶段SQL Server对请求中的T-SQL文本进行语法解析并生成表示请求的抽象语法树(abstract syntax tree)。整个请求会被解析和编译。如果在这个阶段产生错误,则会返回编译错误,并结果任务并释放task和worker。

编译T-SQL不会产生像本地CPU指令一样的可执行代码,也产生类似于字节码的东西。它产生查询计划(Query Plan)。查询计划描述了数据访问的路径和访问对象的方法。

5. 优化(Optimization)

优化是从很多个查询计划中选择出最优的一个。SQL Server采用基于成本的优化器。它会估算所有可能(大多数)的查询计划的成本,并选择出成本最低的一个。成本主要通过计算查询计划需要读取的数据大小(data size)。为了知道数据大小,SQL Server需要知道每个表的大小和列值的分布情况(通过统计信息数据)。成本还会考虑CPU和内存使用量。再通过一个公式将这些数据综合个成本值,然后选取出成本值最小的那个执行计划。

优化过程需要消耗时间和CPU,所以一当查询计划最终生成,则会被缓存到计划缓存中,以备重用。

6. 执行(Execution)

一旦优化器选定了执行计划,请求就可以开始执行了。执行计划会被转换成实际的执行树。树中的每个节点是一个操作符。所有操作符都实现三个抽象接口:open()、next()和close()。循环执行包括调用根节点的open(),然后逐级调用next()直到返回false,再调用close()。

叶级节点通常是一些物理数据访问操作符(访问实际的数据和索引),中间节点通常是一些实现数据过滤、排序和连接等数据操作的操作符。并行执行有一个专门的操作符:Exchange操作符。Exchange操作符发出多个线程,每个线程执行一个查询计划的子树,然后再使用multiple-producers-one-consumer 方式聚合所有子线程的输出。

数据修改操作也适用于这个执行方式。

有些操作符非常简单,如TOP(N)。当调用它的next(),它会去调用子节点的next()并记录数据。当重复执行N次后,它就返回false,并终止对子节点的调用和对相应分支子树的迭代执行。

有些操作符非常复杂,如nested loop操作符。这需要跟踪内外子节点循环迭代的位置,调用外节点的next(),值重绕(rewind)内节点并不断调用内节点的next()直到找到匹配的值。

有些操作符需要等到获取到它的所有子节点的输出数据时,才能产生自己的输出数据。这种行为方式也叫stop-and-go。如sort操作符,它第一次调用netxt(),不会返回数据,需要等到所有的数据被返回并排序,这才能返回数据。

HASH JOIN是一个非常复杂并且又是stop-and-go类型的操作符。为了构造hash表,它要调用构建侧(build side)节点的next(),直到返回false。然后再调用探测侧(probe side)的next(),直到找到在hash表中找到匹配的值,然后返回。重复探测侧的操作,直到next()返回false。

7. 返回结果(Results)

查询一旦开始执行就可以开始返回数据给客户端程序。当执行树开始产生返回数据后,最顶端的操作符会负责把数据写入网络缓存并发送给客户端。执行中产生的返回结果,不会被缓存到任何地方,一但产生就开始返回给客户端。

显然,通过网络返回数据给客户端会受到网络流量控制协议的约束。如果客户端不能及时地取走返回的数据,最终会阻塞数据发送方的发送行为,并使得查询执行被挂起。当客户端的数据接收能力正常后,发送方的发送行为和查询执行会被重置,正常产生返回结果数据。

OUTPUT参数的输出值,只能在执行计划完成后,才能被写入到数据流中。所以它也只能在所有返回结果被客户端取走后,才能被读取到。

总结:

1. 这是一篇译文,计划分为3部分。学习之用,非逐字翻译,很多是结合自己的理解译的,与原文内容相比,有一些增和删。

2. 原文地址:Understanding how SQL Server executes a query

时间: 2024-08-26 14:47:20

理解SQL Server是如何执行查询的 (1/3)的相关文章

理解SQL Server是如何执行查询的---Joe-T :mvp

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

SQL Server 2012笔记分享-4:理解SQL server实例

每个单独的SQL server实例都有一个windows进程:sqlservr.exe,一个windows下能安装多个实例,多个实例会有多个sqlservr.exe进程. 一个SQL实例在后台对应一个服务,如果多个应用程序放在一个实例里,如果某个应用开发的程序有问题,比如死循环,会导致服务停止,从而导致所有数据库无法工作.可以采用多实例分开方式. 一个服务器上可以装多个实例,标准版(16个)和企业版(50个)支持的实例数量不同. SQL server实例的类型 (一)默认实例和命名实例 1.服务

理解SQL SERVER中的分区表(转)

简介 分区表是在SQL SERVER2005之后的版本引入的特性.这个特性允许把逻辑上的一个表在物理上分为很多部分.而对于SQL SERVER2005之前版本,所谓的分区表仅仅是分布式视图,也就是多个表做union操作. 分区表在逻辑上是一个表,而物理上是多个表.这意味着从用户的角度来看,分区表和普通表是一样的.这个概念可以简单如下图所示: 而对于SQL SERVER2005之前的版本,是没有分区这个概念的,所谓的分区仅仅是分布式视图: 本篇文章所讲述的分区表指的是SQL SERVER2005之

正确理解SQL Server配置timeout相关选项

正确理解SQL Server配置选项"remote login timeout"和"remote query timeout" 查看配置选项的设置 sp_configure 远程登录超时 参考:https://msdn.microsoft.com/en-us/library/ms175136.aspx "The remote login timeout option specifies the number of seconds to wait befor

[SQL] 理解SQL SERVER中的逻辑读,预读和物理读

SQL SERVER数据存储的形式 在谈到几种不同的读取方式之前,首先要理解SQL SERVER数据存储的方式.SQL SERVER存储的最小单位为页(Page).每一页大小为8k,SQL SERVER对于页的读取是原子性,要么读完一页,要么完全不读,不会有中间状态.而页之间的数据组织结构为B树(请参考我之前的博文).所以SQL SERVER对于逻辑读,预读,和物理读的单位是页. SQL SERVER一页的总大小为:8K 但是这一页存储的数据会是:8K=8192字节-96字节(页头)-36字节(

深入理解SQL Server 2005 中的 COLUMNS_UPDATED函数

原文:深入理解SQL Server 2005 中的 COLUMNS_UPDATED函数 概述 COLUMNS_UPDATED函数能够出现在INSERT或UPDATE触发器中AS关键字后的任何位置,用来指示表或视图中有哪些列已被插入或者更新.它通常和IF语句一起使用,从而可以根据不同的结果,促使触发器执行不同的操作.因此在DML触发器中,COLUMNS_UPDATED函数是一个非常重要且有用的函数. 不同于UPDATE函数,COLUMNS_UPDATED函数可以工作在多个列中,它使用字节中的位(B

T-SQL查询进阶--理解SQL Server中索引的概念,原理以及其他

简介 在SQL Server中,索引是一种增强式的存在,这意味着,即使没有索引,SQL Server仍然可以实现应有的功能.但索引可以在大多数情况下大大提升查询性能,在OLAP中尤其明显.要完全理解索引的概念,需要了解大量原理性的知识,包括B树,堆,数据库页,区,填充因子,碎片,文件组等等一系列相关知识,这些知识写一本小书也不为过.所以本文并不会深入讨论这些主题. 索引是什么 索引是对数据库表中一列或多列的值进行排序的一种结构,使用索引可快速访问数据库表中的特定信息. 精简来说,索引是一种结构.

理解SQL Server的查询内存授予(译)

此文描述查询内存授予(query memory grant)在SQL Server上是如何工作的,适用于SQL 2005 到2008. 查询内存授予(下文缩写为QMG)是用于存储当数据进行排序和连接时的临时中间数据行.查询在实际执行前需要先请求保留内存,所以会存在一个授予的动作. 这样的好处是提高查询的可靠性和避免单个查询占用所有的内存. SQL Server在收到查询时,会执行3个被定义好的步骤来返回用户所请求的结果集. 1.生成编译计划.它包括各种逻辑指令,如怎么联接数据行. 2.生成执行计

T-SQL查询进阶—理解SQL Server中的锁

在SQL Server中,每一个查询都会找到最短路径实现自己的目标.如果数据库只接受一个连接一次只执行一个查询.那么查询当然是要多快好省的完成工作.但对于大多数数据库来说是需要同时处理多个查询的.这些查询并不会像绅士那样排队等待执行,而是会找最短的路径执行.因此,就像十字路口需要一个红绿灯那样,SQL Server也需要一个红绿灯来告诉查询:什么时候走,什么时候不可以走.这个红绿灯就是锁. 图1.查询可不会像绅士们那样按照次序进行排队 为什么需要锁 在开始谈锁之前,首先要简单了解一下事务和事务的