MSSQL之执行计划介绍
Tags: MSSQLSQL Server执行计划
生成执行计划
看SQL语句执行计划有三种方式:
①快捷键按Ctrl+L;
②选中要执行的SQL然后点击右键,弹出的菜单里面选“显示估计的执行计划”;
③按Ctrl+M打开显示执行计划窗口,选择每次执行SQL都会显示出相应的执行计划
查看分析执行计划
执行计划的图表是从右向左看的
解读SQL执行计划
SQL Server查找数据记录的几种方式:
1.Table Scan--表扫描(最慢),对表记录逐行进行检查,对于没有索引或者查询条件不走索引时会进行全表扫描;
2.Clustered Index Scan--聚集索引扫描(较慢),按聚集索引对记录逐行进行检查,对有主键/聚集索引的表进行无条件查找或者使用主键/聚集索引过滤;
3.Index Scan--索引扫描(普通),根据索引滤出部分数据在进行逐行检查,;
4.Index Seek--索引查找(较快),根据索引定位记录所在位置再取出记录,建立非聚集索引并把其他显示列加入索引中;
5.Clustered Index Seek--聚集索引查找(最快),直接根据聚集索引获取记录,建立非聚集索引并把其他显示列加入索引中并把聚集索引列当作条件;
6.Key Lookup--书签查找:通过非聚集索引找到所求的行,但这个索引并不包含显示的列,因此还要额外去基本表中找到这些列,所以要进行键查找,如果基本表在堆中则Key Lookup会变成RID查找。
7.RID--书签查找:同上
如果有些SQL执行很慢,可以用执行计划看一下是否包含太多“扫描”操作,可以考虑为这些字段建立索引,建立索引切记不要再经常有更新操作的字段上建立,每次更新数据和插入数据都会导致重建索引的操作,会增加索引的维护成本。
示例
⑴没有主键的表查询[表扫描]
⑵有主键的表查询[聚集索引扫描]
⑶建立非聚集索引的表查询[索引扫描+书签查找]
书签查找:通过非聚集索引找到所求的行,但这个索引并不包含显示的列,因此还要额外去基本表中找到这些列,所以要进行键查找,如果基本表在堆中则Key Lookup会变成RID查找,这两个查找统称为书签查找。
⑷建立非聚集索引并把其他显示列加入索引中[索引查找]
⑸建立非聚集索引并把其他显示列加入索引中并把聚集索引列当作条件[聚集索引查找]
数据join方式
在SQL Server中,每个join命令,都会在内部执行时采用这几种更具体的方式来运行:
Nested Loops join:如果一个联接输入很小,而另一个联接输入很大而且已在其联接列上创建了索引, 则索引 Nested Loops 连接是最快的联接操作,因为它们需要的 I/O 和比较都最少。嵌套循环联接也称为“嵌套迭代”,它将一个联接输入用作外部输入表(显示为图形执行计划中的顶端输入),将另一个联接输入用作内部(底端)输入表。外部循环逐行处理外部输入表。内部循环会针对每个外部行执行,在内部输入表中搜索匹配行。最简单的情况是,搜索时扫描整个表或索引;这称为“单纯嵌套循环联接”。如果搜索时使用索引,则称为“索引嵌套循环联接”。如果将索引生成为查询计划的一部分(并在查询完成后立即将索引破坏),则称为“临时索引嵌套循环联接”。
如果外部输入较小而内部输入较大且预先创建了索引,则嵌套循环联接尤其有效。在小事务中(只影响较少数据的事务),索引嵌套循环联接优于合并联接和哈希联接,但在大型查询中,嵌套循环联接通常不是最佳选择。
Merge Join:如果两个联接输入并不小但已在二者联接列上排序(例如,如果它们是通过扫描已排序的索引获得的),则合并联接是最快的联接操作。如果两个联接输入都很大,而且这两个输入的大小差不多,则预先排序的合并联接提供的性能与哈希联接相近。但是,如果这两个输入的大小相差很大,则哈希联接操作通常快得多。合并联接要求两个输入都在合并列上排序,而合并列由联接谓词的等效 (ON) 子句定义。通常,查询优化器扫描索引(如果在适当的一组列上存在索引),或在合并联接的下面放一个排序运算符。在极少数情况下,虽然可能有多个等效子句,但只用其中一些可用的等效子句获得合并列。
Hash Join:哈希联接可以有效处理未排序的大型非索引输入。它们对复杂查询的中间结果很有用,因为:①.中间结果未经索引(除非已经显式保存到磁盘上然后创建索引),而且通常不为查询计划中的下一个操作进行适当的排序。②.查询优化器只估计中间结果的大小。由于对于复杂查询,估计可能有很大的误差,因此如果中间结果比预期的大得多,则处理中间结果的算法不仅必须有效而且必须适度弱化。哈希联接可以减少使用非规范化。非规范化一般通过减少联接操作获得更好的性能,尽管这样做有冗余之险(如不一致的更新)。哈希联接则减少使用非规范化的需要。哈希联接使垂直分区(用单独的文件或索引代表单个表中的几组列)得以成为物理数据库设计的可行选项。
执行过程分析
1.图形的执行计划:
我们将用鼠标点击每个执行计划的节点,可以看到如下数据,各节点的查询数据方式、资源占用情况都可一目了然的看到。
并且系统会给出缺少索引的提示,我们右键点击即可选择“缺少索引详细信息”,会自动生成创建索引的语句。
2.表格的执行计划:
输入以下语句来获取表格样式的执行计划
1 2 3 4 5 | set statistics profile on select *,name from test_index where name='Tom' union ALL select *,name from test_index where age>=12 |
如下图,执行查询后,得到二个表格,上面的表格显示了查询的结果,下面的表格显示了查询的执行过程。相比图形方式展示的执行计划, 这种表格可能在展现上不太直观,但是它能反映更多的信息,而且尤其在比较复杂的查询时看起来更容易,因为对于复杂的查询,【执行计划】的步骤太多,图形方式会造成图形过大,不容易观察,需要上下左右不停拖动。
字段解释:
【Rows】:表示在一个执行步骤中,所产生的记录条数。
【Executes】:表示某个执行步骤被执行的次数。
【Stmt Text】:表示要执行的步骤的描述。
【EstimateRows】:表示要预期返回多少行数据。
在这个【执行过程表格】中,对于优化查询来说,前三列是比较重要的。 前二列的数字告诉我们每个步骤所花的成本,对于比较慢的查询中,应该留意它们。 【Stmt Text】告诉我们每个步骤做了什么事情。一行就表示在图形方式下的一个节点。
执行计划之连接查询篇
1 2 3 4 5 6 7 8 9 10 11 12 | --------------------嵌套循环-------------------- /* UserInfo表数据少、Coupon表数据多嵌套循环可以理解为就是两层For循环,外层For会循环其中的每一项,内层For进行匹配, 相应的外层For对应外部输入表,执行计划的图示排在上面,内层For对应内部出入表,执行计划的图示排在下面,外部表每一行都要使用来匹配, 而内部表却不一定每一行都在匹配中被使用,所以, 1、外部表输入越小越好,也可以利用索引来减少输入行数 2、内部表匹配则可以利用索引来减少匹配条件的范围,尽快获取匹配行 3、多大多数情况下,查询优化器会自动更替结果集小的表为外部,大的为内部当两个Join的表外部输入结果集比较小,而内部输入所查找的表非常大时,查询优化器更倾向于选择循环嵌套方式。 */ SELECT * FROM dbo.UserInfo AS u INNER JOIN dbo.Coupon AS c ON u.Id = c.UserId |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | --------------------合并连接-------------------- /* UserInfo表数据少、Coupon表数据多 不同于循环嵌套的是,合并连接是从每个表仅仅执行一次访问,对于两个输入列都有序的情况下,合并连接的效率更高, 其中排序的的重要性毋庸置疑了,B树中的叶层就是按照一定的逻辑顺序维护的。也就是说,聚集索引和非聚集覆盖索引, 都可以通过对叶层的有序扫描以较小的代价就可以获取有序的数据。在这种情况下,就算输入表的规模比较大,合并联接也相当给力。 如果计划分析器确定连接的一侧记录集中的元素是唯一确定的,那么就会采用一对多的匹配方式(多指另一侧的元素会有重复), 在这种情况下,合并排序效率应该是几种连接方式中最高的。但如果所需的数据列并不存在上述的条件的时候,对于较大的输入来说排序 往往是一个开销非常大的操作(因为基于比较的排序最快也就是n log n的),因此优化器通常不会在这种情况下选用合并联接。 但是对于较小的输入排序的消耗还是可以接受的。合并连接需要双方有序,并且要求join的条件为等号,如果输入数据的双方无序, 则查询分析器不会选择合并连接,我们也可以通过索引提示强制使用合并连接,这就是SQL语句为什么要加OPTION(MERGE JOIN)的原因 */ CREATE NONCLUSTERED INDEX Index_Coupon_UserId ON dbo.Coupon(UserId) --DROP INDEX Index_Coupon_UserId ON dbo.Coupon SELECT * FROM dbo.UserInfo AS u INNER JOIN dbo.Coupon AS c ON u.Id = c.UserId --OPTION(MERGE JOIN) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | --------------------哈希连接-------------------- /* 散列连接同样仅仅只需要只访问1次双方的数据。散列连接通过在内存中建立散列表实现。 这比较消耗内存,如果内存不足还会占用tempdb。但并不像合并连接那样需要双方有序。 删除掉UserInfo的主键及其中的聚集索引,在执行以下SQL 要删除掉聚集索引,否则两个有序输入SQL Server会选择代价更低的合并连接。 SQL Server利用两个上面的输入生成哈希表,下面的输入来探测,可以在属性窗口看到这些信息, 通常来说,所求数据在其中一方或双方没有排序的条件达成时,会选用哈希匹配。 */ ALTER TABLE dbo.UserInfo DROP CONSTRAINT PK_UserInfo_Id --删除主键 --DROP INDEX Index_UserInfo_Name --删除聚集索引 --ALTER TABLE dbo.UserInfo ADD CONSTRAINT PK_UserInfo_Id PRIMARY KEY CLUSTERED(Id) --创建主键 SELECT * FROM dbo.UserInfo AS u INNER JOIN dbo.Coupon AS c ON u.Id = c.UserId |
1 2 3 4 5 6 7 8 9 | --------------------多表并行-------------------- /* 当多个表连接时,SQL Server还允许在多CPU或多核的情况下允许查询并行,这样无疑提高了效率。 */ SELECT * FROM dbo.UserInfo AS u INNER JOIN dbo.Coupon AS c ON u.Id = c.UserId INNER JOIN dbo.OneWayAirPolicy_20w AS o ON u.Id = o.PId |
执行计划之函数计算部分
1 2 3 4 5 6 7 8 9 10 | --------------------标量聚合-------------------- 标量聚合-主要在聚合函数操作中产生 计算标量:根据行中的现有值计算出一个新值 流聚合:在相应排序的流中,计算多组行的汇总值 所有的聚合函数都会有流聚合出现,但是其不会消耗IO,只消耗CPU 除MAX()和MIN()外其他聚合函数都会同时出现标量和聚合两个操作 当列列表只包含聚合函数时,则结果集只具有一个行给出的聚合值,该值由与WHERE子句相匹配的源行计算得到。 SELECT MAX(Age) FROM dbo.UserInfo SELECT COUNT(*) FROM dbo.UserInfo |
1 2 3 4 5 6 7 | /* 执行以下语句,你会发现对[Id]进行去重由于是主键不会有重复,所以直接 通过流聚合就可以计算出结果,而[Name]字段进行去重的时候会有一个Sort排序的操作, 排序是比较消耗资源的尤其在数据量较大的表中,所以我们可以针对这个进行一下优化 */ SELECT COUNT(DISTINCT Id) FROM dbo.UserInfo SELECT COUNT(DISTINCT Name) FROM dbo.UserInfo |
1 2 3 4 5 6 7 | /* 为[Name]字段建立一个非聚集索引再执行一下,会发现出现两个流聚合却没有了排序, 这样就节省了排序的开销,标量聚合算法比较简单,适合非重复值的聚合操作,调优时 尽量避免排序的产生,将分组(GROUP BY)字段锁定在索引覆盖范围内 */ DROP INDEX dbo.UserInfo.Index_UserInfo_Name CREATE INDEX Index_UserInfo_Name ON dbo.UserInfo(Name) |