SQL Server 架构:数据库引擎的「三权分立」
你有没有想过,一条 SELECT 语句从客户端发出,到最终返回结果,SQL Server 内部经历了什么?
很多人只会说「查询数据库」。但如果你追问:连接怎么建立?语法怎么解析?数据怎么从磁盘到内存?十个人里有九个答不上来。
今天,我们来揭开 SQL Server 架构的神秘面纱。
数据库引擎的三层架构
SQL Server 数据库引擎可以分为三层:关系引擎、存储引擎、和 SQL OS。每一层各司其职,缺一不可。
┌─────────────────────────────────────────────────────────┐
│ 关系引擎(Relational Engine) │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────────┐ │
│ │ 命令解析 │→ │ 查询优化 │→ │ 执行计划生成 │ │
│ └──────────┘ └──────────┘ └──────────────────────┘ │
└────────────────────────┬────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ 存储引擎(Storage Engine) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌───────┐ │
│ │ 访问方法 │ │ 锁管理 │ │ 日志管理 │ │ 缓冲池 │ │
│ └──────────┘ └──────────┘ └──────────┘ └───────┘ │
└────────────────────────┬────────────────────────────────┘
↓
┌─────────────────────────────────────────────────────────┐
│ SQL OS │
│ 调度器、内存管理、同步机制、 Worker 线程池 │
└─────────────────────────────────────────────────────────┘关系引擎:数据库的「大脑」
关系引擎负责处理查询请求,核心组件包括:
命令解析器(Parser) 负责将 T-SQL 语句转换为解析树,检查语法错误,生成逻辑操作符。
绑定器(Binder) 将解析树与系统目录关联,检查对象是否存在、权限是否足够,生成查询的内部表示。
查询优化器(Optimizer) 是关系引擎最复杂的部分。它会根据统计信息生成多个执行计划,选择成本最低的那个。这里有个面试常考点:优化器生成的执行计划一定是最优的吗? 答案是否定的,因为优化器使用的是启发式算法,时间有限,不可能穷尽所有方案。
执行引擎(Executor) 按照执行计划一步步执行,从存储引擎请求数据,组装结果集返回。
存储引擎:数据的「仓库管理员」
存储引擎负责数据的物理存储和访问,核心组件包括:
访问方法(Access Methods) 提供读写数据的接口,包括堆表、聚集索引、非聚集索引的访问方式。
锁管理器(Lock Manager) 负责实现事务隔离级别,处理并发控制。你在事务与锁概览中会详细了解。
日志管理器(Log Manager) 确保事务的持久性,记录所有修改操作。事务日志是 SQL Server 恢复能力的基石。
缓冲池(Buffer Pool) 是 SQL Server 的内存核心,缓存数据页和执行计划。数据不会直接从磁盘读取,而是先加载到缓冲池。
SQL OS:底层调度员
SQL OS 是 SQL Server 2008 引入的组件,位于 Windows 操作系统和数据库引擎之间,提供:
任务调度 基于 Windows SOS(SQLOS)的调度器,将 worker 线程映射到 CPU 逻辑核心。每个调度器负责特定 CPU 核心,worker 交替执行。
内存管理 统一管理数据库引擎各组件的内存分配,避免内存碎片化。
同步原语 提供 latch、spinlock 等底层同步机制。
查询执行的整体流程
了解了三层架构,我们来看一条查询的完整旅程:
/**
* SQL Server 查询执行流程(简化模型)
* 实际执行远比这复杂,包含并行执行、分布式查询等
*/
public class QueryExecutionFlow {
/**
* 模拟 SQL Server 查询处理流程
*
* @param sql 用户提交的 SQL 语句
* @return 查询结果
*/
public Object executeQuery(String sql) {
// 第一阶段:连接管理
// TDS 协议建立会话,验证身份,设置环境选项
ConnectionHandler handler = establishConnection(sql);
// 第二阶段:解析与绑定
// 语法检查 → 生成解析树 → 绑定对象和权限
ParseTree tree = Parser.parse(sql);
BoundTree bound = Binder.bind(tree);
// 第三阶段:查询优化
// 生成候选计划 → 估算成本 → 选择最优计划
ExecutionPlan plan = Optimizer.optimize(bound);
// 第四阶段:执行计划
// 缓冲区查找或磁盘读取 → 数据处理 → 结果组装
ResultSet result = Executor.execute(plan);
// 第五阶段:返回结果
// 通过 TDS 协议返回客户端
return sendResult(result);
}
}协议层:客户端与服务端的对话
SQL Server 使用 TDS(Tabular Data Stream)协议 进行客户端与服务端的通信。你平时用的 SSMS、JDBC、ADO.NET,背后都是 TDS 在工作。
TDS 会话生命周期:
- 建立连接 - 客户端发送登录包,服务端验证用户名密码
- 执行命令 - 客户端发送 SQL 命令,服务端返回结果
- 断开连接 - 释放资源,清理会话状态
有意思的是,JDBC 连接 SQL Server 时,驱动会帮你封装 TDS 协议的细节。但如果你想排查连接问题,了解这个协议会很有帮助。
内存结构:数据页的缓存池
SQL Server 的内存使用遵循一个原则:能用内存就不访问磁盘。
缓冲池(Buffer Pool)是 SQL Server 的核心内存区域,结构如下:
┌─────────────────────────────────────────┐
│ Buffer Pool │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Page 1 │ │ Page 2 │ │ Page 3 │ ... │
│ │ (数据) │ │ (数据) │ │ (索引) │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ 每个 Page 大小:8KB │
└─────────────────────────────────────────┘数据页是 SQL Server 最基本的存储单位,大小固定为 8KB。当查询需要某行数据时:
- 检查该页是否在缓冲池中
- 如果在(命中),直接读取
- 如果不在(未命中),从磁盘读取并放入缓冲池
- 如果缓冲池满了,触发页面置换算法(LRU)
这就是为什么数据库服务器内存越大,性能通常越好——能缓存更多数据页,减少磁盘 I/O。
进程架构:从开放数据服务到 SQL Server
在早期版本中,SQL Server 使用 MSSQLServer 和 SQLServerAgent 两个 Windows 服务进程。
从 SQL Server 2005 开始,引入了开放数据服务(Open Data Services,ODS)架构,将监听、连接管理、查询处理等拆分为独立组件:
┌────────────────┐ ┌────────────────┐
│ 客户端应用 │ ←→ │ TDS 监听器 │
└────────────────┘ └───────┬────────┘
↓
┌────────────────┐
│ 连接管理器 │
└───────┬────────┘
↓
┌────────────────┐
│ 查询处理器 │
└───────┬────────┘
↓
┌────────────────┐
│ 存储引擎 │
└────────────────┘这种架构让 SQL Server 能够更高效地管理数千个并发连接。
总结
SQL Server 架构可以用三句话概括:
- 关系引擎负责「想」——解析、优化、生成执行计划
- 存储引擎负责「做」——读写数据、管理事务、控制并发
- SQL OS负责「调度」——分配 CPU 时间片、管理内存、协调各组件
理解这个架构,是深入学习 SQL Server 的前提。
面试追问方向:
- 为什么 SQL Server 使用 8KB 作为数据页大小?这个值可以调整吗?
- 如果缓冲池满了,SQL Server 如何决定将哪些页面换出?什么是 Checkpoint?
- SQL Server 和 MySQL 在架构上有什么本质区别?
这些追问的答案,都在后续章节中。
