1. 概述
在我们使用MSSQL Server 进行复杂查询时,我们通常不会考虑查询优化的过程,我们只需要知道我们的查询语句是如何实现的即可。然而,在MSSQL Server 中执行一条查询语句,实际上是一个复杂的过程,该过程在MSSQL Server中被称为查询优化器。查询优化器负责根据查询语句的条件和选项选择最优化的查询计划,以便尽可能快地返回结果。
本文将详细介绍MSSQL Server 查询优化器的机制,以便开发人员了解其工作原理及如何针对查询语句进行优化。
2. 查询优化器的组成部分
MSSQL Server 查询优化器由以下三部分组成:
2.1 解析器
解析器负责将SQL查询语句转换为内部查询表示。在这一阶段,解析器验证查询语句是否符合语法,是否包含正确的列名和表名,以及是否存在潜在的语义错误。
例如,下面是一个简单的 SELECT 语句:
SELECT * FROM Sales.SalesOrderHeader WHERE DueDate < '2008-07-01';
这个语句将被解析器解析成内部查询表示。解析器需要检查是否存在Sales.SalesOrderHeader表、它是否包含DueDate列,这个语句是否包含正确的语法,等等。
2.2 查询重写器
查询重写器负责修改查询语句以优化查询性能。例如,它可以将OR替换为UNION,删除多余的JOIN条件,或将WHERE子句中的表达式重写为更简单的等效形式。
例如,以下查询语句:
SELECT * FROM Sales.SalesOrderHeader WHERE DueDate < '2008-07-01' OR SalesPersonID = 274;
可以被查询重写器重写成一个UNION查询,如下所示:
SELECT * FROM Sales.SalesOrderHeader WHERE DueDate < '2008-07-01' UNION SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = 274;
2.3 查询优化器引擎
查询优化器引擎基于内部查询表示,生成最优化的查询计划。查询优化器引擎拥有丰富的优化策略,能够根据多种因素和算法生成最优化的查询计划。这些因素包括表的大小、索引的选择、I/O和CPU成本的估算等等。
例如,在确定查询计划时,查询优化器引擎需要考虑以下因素:
3. 查询优化器的工作流程
查询优化器的工作流程可以被概括地描述为:
获取查询语句
解析器将查询语句转换成内部查询表示
查询重写器修改内部查询表示以优化查询性能
查询优化器引擎生成优化的查询计划
执行返回结果
3.1 获取查询语句
获取查询语句是查询优化器的第一步。查询优化器通常从磁盘或缓存中读取查询语句。如果查询语句已经在缓存中,则可以直接从缓存中读取,否则必须从磁盘读取。
内存中缓存查询计划的好处在于,可以节省解析器和查询重写器的时间。这尤其适用于重复查询的情况,例如在存储过程、触发器等场景下。
要查看MSSQL Server中的缓存查询计划,可以使用以下查询:
SELECT objtype, usecounts, size_in_bytes, query_text FROM sys.dm_exec_cached_plans CROSS APPLY sys.dm_exec_sql_text(plan_handle)
3.2 解析器
解析器负责将SQL查询语句解析成内部查询表示。它首先检查语法是否正确,然后继续处理查询语句,确定它的结构和含义。这个阶段通常被称为语义分析或语境分析。
例如,对于以下查询语句:
SELECT * FROM Sales.SalesOrderHeader WHERE DueDate < '2008-07-01';
解析器需要检查是否存在Sales.SalesOrderHeader表、它是否包含DueDate列,这个语句是否包含正确的语法。
3.3 查询重写器
查询重写器是查询优化器的下一步,它负责优化查询的语义。查询重写器将执行以下任务:
从WHERE子句中推导出待选择的列
将OR转换成UNION
删除多余的JOIN条件
修改查询以使用覆盖索引等
例如,对于以下查询语句:
SELECT * FROM Sales.SalesOrderHeader WHERE DueDate < '2008-07-01' OR SalesPersonID = 274;
查询重写器可能会将其重写为一个UNION查询:
SELECT * FROM Sales.SalesOrderHeader WHERE DueDate < '2008-07-01' UNION SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = 274;
3.4 查询优化器引擎
查询优化器引擎是查询优化器的核心,它负责根据内部查询表示生成最优化的查询计划。下面是查询优化器引擎的工作流程:
建立查询需求
收集数据和统计信息
选择查询策略
生成查询计划
3.4.1 建立查询需求
查询优化器首先分析查询需求。查询需求指的是查询的目的。例如,查询目的可能是返回某个特定的结果集,或者是返回排序后的结果集。查询需求的明确表述使得查询优化器可以更准确地选择查询策略。
3.4.2 收集数据和统计信息
查询优化器引擎必须了解它所查询的对象的统计信息,这些对象包括表、列、索引等。为了收集统计信息,MSSQL Server使用了多种方法,包括直接从系统表中获取,或从执行计划中获取。
3.4.3 选择查询策略
查询优化器引擎选择查询策略的核心算法是代价估算器(cost estimator)。代价估算器会根据所需的操作、访问的对象和数据的大小来估算每个操作的代价。
查询优化器引擎通常会生成多种可能的查询计划,并使用代价估算器计算每个计划的代价。然后,查询优化器引擎分析代价估算器的结果,选择代价最小的查询计划。
3.4.4 生成查询计划
查询优化器引擎在选择了最佳查询策略后,会根据这个策略生成查询计划。查询计划是一组指令,指示MSSQL Server 如何执行查询语句。查询计划指定了访问数据的方法、使用的索引、访问方法以及计算方式等,最终返回查询结果集。
要查看查询计划,可以在 SQL Server Management Studio (SSMS) 中从上下文菜单中选择“显示执行计划”,或者直接使用以下查询语句:
SET SHOWPLAN_ALL ON
GO
SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = 274 AND DueDate < '2008-07-01';
GO
SET SHOWPLAN_ALL OFF
4. 查询优化器的性能优化
在优化查询性能时,查询优化器是一个非常重要的因素。以下是一些可以优化查询优化器的性能的技巧。
4.1 使用参数化查询
参数化查询可以显著减少存储过程和应用程序执行的查询次数,从而减轻查询优化器的负担。
当应用程序使用参数化查询时,查询优化器可以使用缓存的查询计划,而不是针对每个查询生成新的查询计划。
以下是一个参数化查询的示例:
DECLARE @SalesPersonID INT, @DueDate DATETIME;
SELECT * FROM Sales.SalesOrderHeader WHERE SalesPersonID = @SalesPersonID AND DueDate < @DueDate;
4.2 索引优化
索引可以显著提高查询性能,但它们也会增加插入、更新和删除的开销。
每个数据表应该至少有一个聚集索引(clustered index),它可以帮助确定数据的物理存储顺序。此外,它最好为数据表的每个常见查询创建至少一个非聚集索引(nonclustered index)。
4.3 约束条件的正确性
查询优化器在决定最优化的查询计划时,要求提供的条件正确,并且表中数据的存在和访问方式与优化器的假设相同。
例如,如果查询时使用了字符转换函数,会导致该查询无法利用索引。使用了空间搜索时,确定索引的选择务必考虑地理空间类型和地理空间参考系统等。
5. 总结
查询优化器是MSSQL Server的一个重要组成部分,它根据查询语句的条件和选项选择最优化的查询计划,以便尽可能快地返回结果。MSSQL Server查询优化器由三部分组成:解析器、查询重写器和查询优化器引擎。查询优化器引擎负责根据内部查询表示生成最优化的查询计划。为了优化查询性能,可以使用参数化查询、索引优化、调整约束条件和多种查询优化策略等技巧。