SQL Server 是如何执行查询脚本的呢? 首先, 应用程序连接到 SQL Server 引擎, 向 SQL Server 发送请求. 一旦应用程序连接到数据库引擎, SQL Server 创建会话(Session), 用于表示客户端和服务器端之间数据交换的状态. 其次, SQL Server 引擎分配 Task 来接受查询请求, 然后, SQL Server 把 Workder 绑定到 Task, 开始分配 CPU 资源和内存资源来处理 Task. 最后, 通过解析, 编译和优化, 进入查询引擎, 真正开始执行查询请求. 下图从高层次上概括了 SQL Server 执行 TSQL 脚本的流程:
对于图中的相关组件, 先来了解一下其基本的概念和功能, 下面的术语都是在 SQL Server OS 中实现的.
一, Connections
连接, 这是在底层协议上实现的实际的物理连接, 在客户端向数据库引擎发送请求之前, 必须建立应用程序和数据库引擎之间的物理链接, 是应用程序和数据库引擎之间联系的物理通道, 有三种连接的类型: TCP socket, 命名管道 (named pipe) 和共享内存(shared memory). 相关的 DMV:sys.dm_exec_connections.
二, Sessions
会话, 当客户端应用程序连到 SQL Server 时, 两端就会建立起一个 "会话" 用于交换信息. 严格来说, 会话不是底层的物理连接, 是 SQL Server 对连接的逻辑表示, 用于存储在数据库引擎和应用程序之间连接时所需要的设置, 比如, 登陆信息, 事务的隔离级别, 会话的 SET 选项等. 但是, 在实际应用中, 通常可以把会话视为连接.
当发现一个 session 有多个值时, 意味着出现了并行查询. 一个并行查询使用相同的会话来连接客户端, 但是在 SQL Server 端使用多个 Worker(Thread)用于服务这个请求. 相关 DMV:sys.dm_exec_sessions, 当你看到有多行数据有相同的 Session ID 时, 这是因为 SQL Server 使用多个线程来处理一个查询请求.
通常情况下, 一个 Connection 对应一个 Session, 有时, 可能存在多个 Session 对应一个 Connection 的情况, 这是 MARS(Multiple Active Result Sets)现象.
三, Request
请求, 在 SQL Server 的语境下, 是查询或批处理的逻辑表示. SQL Server 是一个 Client-Server 平台, 客户端与服务器 (后端数据库) 交互的唯一方式是通过发送包含命令的请求到数据库, 而客户端与服务器端交互的协议简称为 TDS(Tabular Data Stream). 应用程序使用 SqlClient,OleDB,ODBC,JDBC 等驱动来实现这种协议. 当应用程序需要对数据库执行任何操作时, 它都通过 TDS 协议向数据库引擎发送一个请求(request).
简单来说, 每次对数据库的操作都会以 "请求" 的形式发送给数据库服务器, 发送请求有多种类型, 主要是: Batch Request,RPC Request,Bulk Load Request.
Batch Request: 批请求, 此请求类型仅包含要执行的批处理的 T-SQL 文本.
RPC Request: 远程过程调用请求(Remote Procedure Call Request), 用于执行存储过程.
Bulk Load Request: 大容量加载请求, 用于执行大容量插入 (Bulk Insert) 操作
相关的 DMV 是: sys.dm_exec_requests.
四, Tasks
任务, 表示 SQL Server 需要处理的一个请求. 一个或多个 Task 会被分配用于完成一个请求. 在一个完整的 TDS 请求达到数据库引擎时, SQL Server 将会创建一个任务 (Task) 来处理请求. 当 Request 到达 SQL Server 之后, 后续操作都发生在 SQL Server 内部. 用户可以从 sys.dm_exec_requests 查看数据库引擎接收到的所有请求. 一旦一个 Task 接收一个请求, Task 的状态由 PENDING 转变为可用.
当任务被创建用于处理请求时, 该 Task 将代表请求从开始到完成的整过程. 例如, 如果请求是 SQL Batch 类型的请求, 则任务将代表整个批次, 而不是单个语句, SQL Batch 中的单个语句不会创建新任务. 批处理中的某些个别语句可以并行执行(通常称为 DOP, 并行度), 在这种情况下, 任务将产生新的子任务以并行执行. 如果请求返回结果, 则当客户端完全使用结果时(例如, 当您处置 SqlDataReader 时), 批处理就完成了. 您可以通过查询 sys.dm_os_tasks 来查看服务器中的任务列表.
当一个新请求到达服务器并且创建一个对应的任务时, 首先会处于 PENDING(挂起状态), 任务的状态可以有:
PENDING: 正在等待工作线程(Worker Thread).
RUNNABLE: 可运行, 但正在等待接收一个时间片(quantum).
RUNNING: 当前正在 Scheduler 中运行.
SUSPENDED: 拥有 worker, 但是正在等待某些事件(向 RUNNABLE 转变)
DONE: 已经完成.
SPINLOOP: 陷入自旋锁.
当新的请求到达服务器并创建任务以处理该请求时, 处于 PENDING 状态. 在此阶段, 服务器尚不知道请求实际上是什么. 该任务必须首先开始执行, 为此, 引擎必须为其分配一个工作程序(Worker).
五, Workers
工作进程(Workder Thread), 简称为 Workder, 或 Thread, 逻辑上对应于操作系统的线程, 线程是操作系统可以执行的最小处理单元, 并允许将应用程序逻辑上分为多个并发执行路径. SQL Server 服务器在启动时会创建一定数量的 Worker(工作程序), 并且可以按需创建更多工作程序, 直到配置的最大工作程序线程(max worker threads). 只有 Worker 才能执行代码, Worker 等待 PENDING 任务变为可用(当 Task 被分配用于处理请求), 然后每个 Workder 被分配到一个 Task, 并执行该 Task.Workder 会一直执行(running), 直到任务完全完成.
当没有更多可用的 Worker(工作进程)时, 正在等待处理 (PENDING) 的任务将不得不等待, 直到正在执行的 (running) 任务完成, 或者执行该任务的 Workder 变得可用, 能够执行下一个 PENDING 的任务为止.
对于一个 SQL 批处理请求, 承担该任务的工作进程将执行整个 SQL 批处理 (每个语句). 对于 SQL 批处理中的语句(=> request => task => worker) 是否可以并行执行, 答案是否定的, 因为它们是在单个线程 (=> worker) 上执行的, 所以每个语句必须按照顺序来执行.
对于使用并行选项 (DOP> 1) 的语句, SQL Server 会创建子任务, 每个子任务都会经历完全相同的周期: 创建子任务(PENDING), 工作程序必须拾取子任务并执行(与 SQL 批处理工作者不同的工作程序), 通过查询 sys.dm_os_workers 可以查看 SQL Server 中工作程序的列表和状态.
六, Scheduler
调度程序 (Scheduler) 是指 SOS scheduler, 用于管理 Worker 对 CPU 时间的需求, 协调各个 Worker 对 CPU 资源的利用. 每一个 Scheduler 都映射到一个单独的 CPU,Workder 在一个调度程序中保持活跃 / 运行 (Running) 的时间称作一个时间片(Quantum), 最长时长为 4 毫秒. 在其时间片到期之后, 一个 Worker 主动退出, 把时间片让给其他需要访问 CPU 资源的 Workder, 并修改自身的状态为 RUNNABLE, 这种调度方式称为非抢占式调度.
SOS Scheduler 是非抢占式的, 数据库对各种请求分配的时间都是相同的, 而操作系统的调度模式抢占式的, 当出现紧急情况时, 按照优先级, 高优先级进程抢占把低优先级低的资源.
参考文档:
- Thread and Task Architecture Guide
- Understanding how SQL Server executes a query
- Understanding how SQL Server executes a query
T-SQL 执行内幕(1)-- 简介
来源: https://www.cnblogs.com/ljhdo/p/12935351.html