logo资料库

SQL执行计划.docx

第1页 / 共17页
第2页 / 共17页
第3页 / 共17页
第4页 / 共17页
第5页 / 共17页
第6页 / 共17页
第7页 / 共17页
第8页 / 共17页
资料共17页,剩余部分请下载后查看
Sql Server 查找记录的方法
Sql Server Join 方式
更具体执行过程
索引统计信息:查询计划的选择依据
优化视图查询
对于 SqlServer 的优化来说,可能优化查询是很常见的事情。关于数据库的优化,本身也是一 个涉及面比较的广的话题, 首先,打开【SQL Server Management Studio】,输入一个 查询语句看看 SqlServer 是如何显示查询计划的吧。 select v.OrderID, v.CustomerID, v.CustomerName, v.OrderDate, v.SumMoney, v.Finished from where v.OrderDate >= '2010-12-1' and v.OrderDate < '2011-12-1'; 其中,OrdersView 是一个视图,其定义如下: SELECT dbo.Orders.OrderDate, dbo.Orders.OrderID, dbo.Orders.CustomerID, OrdersView as v dbo.Orders.SumMoney, dbo.Orders.Finished, ISNULL(dbo.Customers.CustomerName, N'') AS CustomerName FROM dbo.Orders LEFT OUTER JOIN dbo.Customers ON dbo.Orders.CustomerID = dbo.Customers.CustomerID 对于前一句查询,SqlServer 给出的查询计划如下(点击工具栏上的【显示估计的执行计划】 按钮): 从这个图,我们至少可以得到 3 个有用的信息: 1. 哪些执行步骤花费的成本比较高。显然,最右边的二个步骤的成本是比较高的。 2. 哪些执行步骤产生的数据量比较多。对于每个步骤所产生的数据量, SqlServer 的执行计 划是用【线条粗细】来表示的,因此也很容易地从分辨出来。 3. 每一步执行了什么样的动作。 对于一个比较慢的查询来说,我们通常首先要知道哪些步骤的成本比较高,进而,可以尝试一些 改进的方法。 一般来说,如果您不能通过:提高硬件性能或者调整 OS,SqlServer 的设置之 类的方式来解决问题,那么剩下的可选方法通常也只有以下这些了:
1. 为【scan】这类操作增加相应字段的索引。 2. 有时重建索引或许也是有效的,具体情形请参考后文。 3. 调整语句结构,引导 SqlServer 采用其它的查询方案去执行。 4. 调整表结构(分表或者分区)。 下面再来说说一些很重要的理论知识,这些内容对于执行计划的理解是很有帮助的。 Sql Server 查找记录的方法 说到这里,不得不说 SqlServer 的索引了。SqlServer 有二种索引:聚集索引和非聚集索引。 二者的差别在于:【聚集索引】直接决定了记录的存放位置, 或者说:根据聚集索引可以直接 获取到记录。【非聚集索引】保存了二个信息:1.相应索引字段的值,2.记录对应聚集索引的 位置(如果表没有聚集索引则保存记录指针)。 因此,如果能通过【聚集索引】来查找记录, 显然也是最快的。 Sql Server 会有以下方法来查找您需要的数据记录: 1. 【Table Scan】:遍历整个表,查找所匹配的记录行。这个操作将会一行一行的检查,当 然,效率也是最差的。 2. 【Index Scan】:根据索引,从表中过滤出来一部分记录,再查找所匹配的记录行,显示 比第一种方式的查找范围要小,因此比【Table Scan】要快。 3. 【Index Seek】:根据索引,定位(获取)记录的存放位置,然后取得记录,因此,比起 前二种方式会更快。 4. 【Clustered Index Scan】:和【Table Scan】一样。注意:不要以为这里有个 Index, 就认为不一样了。 其实它的意思是说:按聚集索引来逐行扫描每一行记录,因为记录就是按聚 集索引来顺序存放的。 而【Table Scan】只是说:要扫描的表没有聚集索引而已,因此这二 个操作本质上也是一样的。 5. 【Clustered Index Seek】:直接根据聚集索引获取记录,最快! 所以,当发现某个查询比较慢时,可以首先检查哪些操作的成本比较高,再看看那些操作是查找 记录时, 是不是【Table Scan】或者【Clustered Index Scan】,如果确实和这二种操 作类型有关,则要考虑增加索引来解决了。 不过,增加索引后,也会影响数据表的修改动作, 因为修改数据表时,要更新相应字段的索引。所以索引过多,也会影响性能。 还有一种情况是
不适合增加索引的:某个字段用 0 或 1 表示的状态。例如可能有绝大多数是 1,那么此时加索 引根本就没有意义。 这时只能考虑为 0 或者 1 这二种情况分开来保存了,分表或者分区都是不 错的选择。 如果不能通过增加索引和调整表来解决,那么可以试试调整语句结构,引导 SqlServer 采用其 它的查询方案去执行。 这种方法要求:1.对语句所要完成的功能很清楚,2.对要查询的数据表 结构很清楚,3.对相关的业务背景知识很清楚。 如果能通过这种方法去解决,当然也是很好的 解决方法了。不过,有时 SqlServer 比较智能,即使你调整语句结构,也不会影响它的执行计 划。 如何比较二个同样功能的语句的性能好坏呢,我建议采用二种方法:1. 直接把二个查询语句放 在【SQL Server Management Studio】,然后去看它们的【执行计划】,SqlServer 会 以百分比的方式告诉你二个查询的【查询开销】。 这种方法简单,通常也是可以参考的,不过, 有时也会不准,具体原因请接着往下看(可能索引统计信息过旧)。 2. 根据真实的程序调用,写相应的测试代码去调用:这种方法就麻烦一些,但是它更能代表现 实调用情况, 得到的结果也是更具有参考价值的,因此也是值得的。 Sql Server Join 方式 在 Sql Server 中,我们每个 join 命令,都会在内部执行时,采用三种更具体的方式来运行: 1. 【Nested Loops join】,如果一个联接输入很小,而另一个联接输入很大而且已在其联 接列上创建了索引, 则索引 Nested Loops 连接是最快的联接操作,因为它们需要的 I/O 和 比较都最少。 嵌套循环联接也称为“嵌套迭代”,它将一个联接输入用作外部输入表(显示为图形执行计划中 的顶端输入),将另一个联接输入用作内部(底端)输入表。外部循环逐行处理外部输入表。内 部循环会针对每个外部行执行,在内部输入表中搜索匹配行。可以用下面的伪码来理解: foreach(row r1 in outer table) foreach(row r2 in inner table) if( r1, r2 符合匹配条件 ) output(r1, r2);
最简单的情况是,搜索时扫描整个表或索引;这称为“单纯嵌套循环联接”。如果搜索时使用索 引,则称为“索引嵌套循环联接”。如果将索引生成为查询计划的一部分(并在查询完成后立即 将索引破坏),则称为“临时索引嵌套循环联接”。查询优化器考虑了所有这些不同情况。 如果外部输入较小而内部输入较大且预先创建了索引,则嵌套循环联接尤其有效。在许多小事务 中(如那些只影响较小的一组行的事务),索引嵌套循环联接优于合并联接和哈希联接。但在 大型查询中,嵌套循环联接通常不是最佳选择。 2. 【Merge Join】,如果两个联接输入并不小但已在二者联接列上排序(例如,如果它们是 通过扫描已排序的索引获得的),则合并联接是最快的联接操作。如果两个联接输入都很大, 而且这两个输入的大小差不多,则预先排序的合并联接提供的性能与哈希联接相近。但是,如 果这两个输入的大小相差很大,则哈希联接操作通常快得多。 合并联接要求两个输入都在合并列上排序,而合并列由联接谓词的等效 (ON) 子句定义。通常, 查询优化器扫描索引(如果在适当的一组列上存在索引),或在合并联接的下面放一个排序运算 符。在极少数情况下,虽然可能有多个等效子句,但只用其中一些可用的等效子句获得合并列。 由于每个输入都已排序,因此 Merge Join 运算符将从每个输入获取一行并将其进行比较。例 如,对于内联接操作,如果行相等则返回。如果行不相等,则废弃值较小的行并从该输入获得另 一行。这一过程将重复进行,直到处理完所有的行为止。 合并联接操作可以是常规操作,也可以是多对多操作。多对多合并联接使用临时表存储行(会影 响效率)。如果每个输入中有重复值,则在处理其中一个输入中的每个重复项时,另一个输入必 须重绕到重复项的开始位置。 可以创建唯一索引告诉 SqlServer 不会有重复值。 如果存在驻留谓词,则所有满足合并谓词的行都将对该驻留谓词取值,而只返回那些满足该驻留 谓词的行。 合并联接本身的速度很快,但如果需要排序操作,选择合并联接就会非常费时。然而,如果数据 量很大且能够从现有 B 树索引中获得预排序的所需数据,则合并联接通常是最快的可用联接算 法。 3. 【Hash Join】,哈希联接可以有效处理未排序的大型非索引输入。它们对复杂查询的中 间结果很有用,因为:1. 中间结果未经索引(除非已经显式保存到磁盘上然后创建索引),而 且通常不为查询计划中的下一个操作进行适当的排序。2. 查询优化器只估计中间结果的大小。
由于对于复杂查询,估计可能有很大的误差,因此如果中间结果比预期的大得多,则处理中间结 果的算法不仅必须有效而且必须适度弱化。 哈希联接可以减少使用非规范化。非规范化一般通过减少联接操作获得更好的性能,尽管这样做 有冗余之险(如不一致的更新)。哈希联接则减少使用非规范化的需要。哈希联接使垂直分区(用 单独的文件或索引代表单个表中的几组列)得以成为物理数据库设计的可行选项。 哈希联接有两种输入:生成输入和探测输入。查询优化器指派这些角色,使两个输入中较小的那 个作为生成输入。 哈希联接用于多种设置匹配操作:内部联接;左外部联接、右外部联接和完全外部联接;左半联 接和右半联接;交集;联合和差异。此外,哈希联接的某种变形可以进行重复删除和分组,例如 SUM(salary) GROUP BY department。这些修改对生成和探测角色只使用一个输入。 哈希联接又分为 3 个类型:内存中的哈希联接、Grace 哈希联接和递归哈希联接。 内存中的哈希联接:哈希联接先扫描或计算整个生成输入,然后在内存中生成哈希表。根据计算 得出的哈希键的哈希值,将每行插入哈希存储桶。如果整个生成输入小于可用内存,则可以将所 有行都插入哈希表中。生成阶段之后是探测阶段。一次一行地对整个探测输入进行扫描或计算, 并为每个探测行计算哈希键的值,扫描相应的哈希存储桶并生成匹配项。 Grace 哈希联接:如果生成输入大于内存,哈希联接将分为几步进行。这称为“Grace 哈希联 接”。每一步都分为生成阶段和探测阶段。首先,消耗整个生成和探测输入并将其分区(使用哈 希键上的哈希函数)为多个文件。对哈希键使用哈希函数可以保证任意两个联接记录一定位于相 同的文件对中。因此,联接两个大输入的任务简化为相同任务的多个较小的实例。然后将哈希联 接应用于每对分区文件。 递归哈希联接:如果生成输入非常大,以至于标准外部合并的输入需要多个合并级别,则需要多 个分区步骤和多个分区级别。如果只有某些分区较大,则只需对那些分区使用附加的分区步骤。 为了使所有分区步骤尽可能快,将使用大的异步 I/O 操作以便单个线程就能使多个磁盘驱动器 繁忙工作。 在优化过程中不能始终确定使用哪种哈希联接。因此,SQL Server 开始时使用内存中的哈希 联接,然后根据生成输入的大小逐渐转换到 Grace 哈希联接和递归哈希联接。 如果优化器错误地预计两个输入中哪个较小并由此确定哪个作为生成输入,生成角色和探测角色
将动态反转。哈希联接确保使用较小的溢出文件作为生成输入。这一技术称为“角色反转”。至 少一个文件溢出到磁盘后,哈希联接中才会发生角色反转。 说明:您也可以显式的指定联接方式,SqlServer 会尽量尊重您的选择。比如你可以这样写: inner loop join, left outer merge join, inner hash join 但是,我还是建议您不要这样做,因为 SqlServer 的选择基本上都是正确的,不信您可以试一 下。 好了,说了一大堆理论东西,再来个实际的例子来解释一下吧。 更具体执行过程 前面,我给出一张图片,它反映了 SqlServer 在执行某个查询的执行计划,但它反映的信息可 能不太细致,当然,您可以把鼠标指标移动某个节点上,会有以下信息出现: 刚好,我装的是中文版的,上面都是汉字,我也不多说了。我要说的是另一种方式的执行过程, 比这个包含更多的执行信息, 而且是实际的执行情况。(当然,您也可以继续使用图形方式, 在运行查询前点击工具栏上的【包括实际的执行计划】按钮) 让我们再次回到【SQL Server Management Studio】,输入以下语句,然后执行。 set statistics profile on
select v.OrderID, v.CustomerID, v.CustomerName, v.OrderDate, v.SumMoney, v.Finished from where v.OrderDate >= '2010-12-1' and v.OrderDate < '2011-12-1'; 注意:现在加了一句,【set statistics profile on 】,得到的结果如下: OrdersView as v 可以从图片上看到,执行查询后,得到二个表格,上面的表格显示了查询的结果,下面的表格显 示了查询的执行过程。相比本文的第一张图片, 这张图片可能在直观上不太友好,但是,它能 反映更多的信息,而且尤其在比较复杂的查询时,可能看起来更容易,因为对于复杂的查询,【执 行计划】的步骤太多,图形方式会造成图形过大,不容易观察。 而且这张执行过程表格能反映 2 个很有价值的数据(前二列)。 还是来看看这个【执行过程表格】吧。我来挑几个重要的说一下。 【Rows】:表示在一个执行步骤中,所产生的记录条数。(真实数据,非预期) 【Executes】:表示某个执行步骤被执行的次数。(真实数据,非预期) 【Stmt Text】:表示要执行的步骤的描述。 【EstimateRows】:表示要预期返回多少行数据。 在这个【执行过程表格】中,对于优化查询来说,我认为前三列是比较重要的。对于前二列,我 上面也解释了,意思也很清楚。 前二列的数字也大致反映了那些步骤所花的成本,对于比较慢 的查询中,应该留意它们。 【Stmt Text】会告诉你每个步骤做了什么事情。对于这种表格, 它所要表达的其实是一种树型信息(一行就表示在图形方式下的一个节点), 所以,我建议从 最内层开始去读它们。做为示例,我来解释一下这张表格它所表达的执行过程。 第 5 行:【Clustered Index Seek(OBJECT:([MyNorthwind].[dbo].[Customers].[PK_Customers]), SEEK:([MyNorthwind].[dbo].[Customers].[CustomerID]=[MyNorthwind].[dbo]
.[Orders].[CustomerID]) ORDERED FORWARD)】, 意思是说,SqlServer 在对表 Customers 做 Seek 操作,而且是按照【Clustered Index Seek】的方式,对应的索引是 【PK_Customers】,seek 的值来源于[Orders].[CustomerID] 第 4 行:【Clustered Index Scan(OBJECT:([MyNorthwind].[dbo].[Orders].[PK_Orders]), WHERE:([MyNorthwind].[dbo].[Orders].[OrderDate]>='2010-12-01 00:00:00.000' AND [MyNorthwind].[dbo].[Orders].[OrderDate]<'2011-12-01 00:00:00.000'))】, 意思是说,SqlServer 在对表 Customers 做 Scan 操作,即:最差 的【表扫描】的方式,原因是,OrderDate 列上没有索引,所以只能这样了。 第 3 行:【Nested Loops(Left Outer Join, OUTER REFERENCES:([MyNorthwind].[dbo].[Orders].[CustomerID]))】, 意思是说, SqlServer 把第 5 行和第 4 行产生的数据用【Nested Loops】的方式联接起来,其中 Outer 表是 Orders,要联接的匹配操作也在第 5 行中指出了。 第 2 行:【Compute Scalar(DEFINE:([Expr1006]=isnull([MyNorthwind].[dbo].[Customers].[Cus tomerName],N'')))】, 意思是说,要执行一个 isnull()函数的调用。具体原因请参考本 文前部分中给出视图定义代码。 第 1 行:【SELECT [v].[OrderID],[v].[CustomerID],[v].[CustomerName],[v].[OrderDate],[v]. [SumMoney],[v].[Finished] FROM [OrdersView] [v] WHERE [v].[OrderDate]>=@1 AND [v].[OrderDate]<@2】, 通常第 1 行就是整个查询,表示 它的返回值。 索引统计信息:查询计划的选择依据 前面一直说到【执行计划】,既然是计划,就表示要在具体执行前就能确定下来的操作方案。那 么 SqlServer 是如何选择一种执行计划的呢?SqlServer 怎么知道什么时候该用索引或者用 哪个索引? 对于 SqlServer 来说,每当要执行一个查询时,都要首先检查有没有这个查询的 执行计划是否存在缓存中,如果没有,则要生成一个执行计划, 具体在产生执行计划时,并不
分享到:
收藏