前言:

很多很多地方对于语句的优化,一般比较靠谱的回复即使——把执行计划发出来看看。当然那些只看语句就说如何如何改代码,我一直都是拒绝的,因为这种算是纯蒙。根据本人经验,大量的性能问题单纯从语句来看很难发现瓶颈,同一个语句,由于环境的不同,差距非常大,所以比较合适的还是分析执行计划。

那么对于执行计划,一般使用图形化执行计划就差不多了,但是用过的人也有一些疑惑,里面的图标(称为操作符)并不非常直观。所以从本文开始,会整理一些不怎么常见但由比较重要的操作符并进行解释,对于那些表扫描、索引扫描、聚集索引扫描、索引查找、聚集索引查找这些非常常见的操作符,暂时不打算介绍。

只有了解一些重要且常见的操作符,才能对语句进行准确有效的性能分析和优化。

本系列文章预计包含下面操作符:

  1. 断言:Assert (英文版本图形化界面的名字,中文版本中XML格式的执行计划和TEXT格式的执行计划的名字。下同)
  2. 串联:Concatenation
  3. 计算标量:Compute Scalar
  4. 键查找:Key Lookup
  5. 假脱机:Spools
  6. 表假脱机:Lazy Spool
  7. 索引假脱机:Index Spool
  8. 行计数假脱机:Row CountSpool
  9. 流聚合:Stream Aggregate
  10. 排序:Sort
  11. 合并联接:Merge Join
  12. 合并间隔:Merge Interval
  13. 拆分、折叠:Split,Collapse

接下来从断言开始介绍。原文出处:http://blog.csdn.net/dba_huangzj/article/details/50261747

断言:


Assert运算符是一个物理运算符。在执行计划中,如果为中文版图形化执行计划,被称为“断言”,在英文版及非图形化执行计划中显示为Assert。

其图标为:

Assert 运算符用于验证条件。例如,验证引用完整性或确保标量子查询返回一行。对于每个输入行,Assert 运算符都要计算执行计划的 Argument 列中的表达式。如果此表达式的值为 NULL,则通过 Assert 运算符传递该行,并且查询执行将继续。如果此表达式的值非空,则将产生相应的错误。

断言与Check约束:

先来看看这段代码,在服务器执行时,先创建测试环境,使用TempDB是不错的选择:

  1. USE tempdb
  2. GO
  3.  
  4. IF OBJECT_ID('TableAssert') IS NOT NULL
  5. DROP TABLE TableAssert
  6. GO
  7.  
  8. CREATE TABLE TableAssert (
  9. ID INTEGER
  10. ,Gender CHAR(1)
  11. )
  12. GO
  13.  
  14. ALTER TABLE TableAssert ADD CONSTRAINT ck_Gender_M_F CHECK (Gender IN ('M','F'))
  15. GO

选中下面代码,不要执行,选择“显示估计的执行计划”,如图:

代码如下:

  1. INSERT INTO TableAssert(ID ,Gender )
  2. VALUES (1,'X')
  3. GO

从上图可见有一个操作符叫“断言(Assert)”,那么这个里面是什么东西呢?把鼠标移到这个操作符上面可以看到下图:

注意上面的解释:用于验证指定的条件是否存在,这个解释很直观,并且看谓词部分,说明了实际验证的内容,判断Gender字段的插入值是否属于F/M两种,如果不是则返回NULL。

断言操作符会针对验证返回值进行处理,如果验证返回NULL,则返回错误信息,也就是如果你直接执行INSERT语句就可以看到报错:

原文出处:http://blog.csdn.net/dba_huangzj/article/details/50261747

断言与外键约束:


下面来看个关于外键约束的例子:

  1. use tempdb
  2. go
  3. ALTER TABLE TableAssert ADD ID_Genders INT
  4. GO
  5. IF OBJECT_ID('TableFOREIGN') IS NOT NULL
  6. DROP TABLE TableFOREIGN
  7. GO
  8. CREATE TABLE TableFOREIGN(ID Integer PRIMARY KEY, Gender CHAR(1))
  9. GO
  10. INSERT INTO TableFOREIGN(ID, Gender) VALUES(1, 'F')
  11. INSERT INTO TableFOREIGN(ID, Gender) VALUES(2, 'M')
  12. INSERT INTO TableFOREIGN(ID, Gender) VALUES(3, 'N')
  13. GO
  14. ALTER TABLE TableAssert ADD CONSTRAINT fk_Tab2 FOREIGN KEY (ID_Genders) REFERENCES TableFOREIGN(ID)
  15. GO

同样,我们使用估计执行计划测试一下INSERT语句:

语句如下:

  1. INSERT INTO TableAssert(ID, ID_Genders, Gender) VALUES(1, 4, 'X')

这次我们使用另外一个工具:SET SHOWPLAN_TEXT ON 按这种方式执行:

  1. SET SHOWPLAN_TEXT ON
  2. GO
  3. INSERT INTO TableAssert(ID, ID_Genders, Gender) VALUES(1, 4, 'X')

会看到两个结果,第一个是语句,不用关,我们看第二个结果:

  1. |--Assert(WHERE:(CASE WHEN NOT [Pass1009] AND [Expr1008] IS NULL THEN (0) ELSE NULL END))
  2. |--Nested Loops(Left Semi Join, PASSTHRU:([tempdb].[dbo].[TableAssert].[ID_Genders] IS NULL), OUTER REFERENCES:([tempdb].[dbo].[TableAssert].[ID_Genders]), DEFINE:([Expr1008] = [PROBE VALUE]))
  3. |--Assert(WHERE:(CASE WHEN [tempdb].[dbo].[TableAssert].[Gender]<>'F' AND [tempdb].[dbo].[TableAssert].[Gender]<>'M' THEN (0) ELSE NULL END))
  4. | |--Table Insert(OBJECT:([tempdb].[dbo].[TableAssert]), SET:([tempdb].[dbo].[TableAssert].[ID] = [@1],[tempdb].[dbo].[TableAssert].[ID_Genders] = [@2],[tempdb].[dbo].[TableAssert].[Gender] = [Expr1004]), DEFINE:([Expr1004]=CONVERT_IMPLICIT(char(1),[@3],0)))
  5. |--Clustered Index Seek(OBJECT:([tempdb].[dbo].[TableFOREIGN].[PK__TableFOR__3214EC27173876EA]), SEEK:([tempdb].[dbo].[TableFOREIGN].[ID]=[tempdb].[dbo].[TableAssert].[ID_Genders]) ORDERED FORWARD)

这个结果内容较多可能不直观,读者可以执行测试看结果。

可以看到里面有两次Assert,自下而上地阅读,第一个Assert(也就是下面那个,针对于图形化界面而言是右边那个,因为图形化执行计划是从右到左地阅读)是前面用于CHECK约束的,如果返回0则继续运行语句,否则返回错误。

对于第二个Assert用于检测两表关联的结果,其中“[Expr1008] IS NULL”(注意[Expr1008]不是固定的,根据每台机器可能返回不同值,在本人机器上的SQL 2008/2012分别执行都得到不同的[Expr]值),我们需要知道[Expr1008]是什么,内容中有DEFINE:([Expr1008] = [PROBE VALUE]),这就是表关联的结果。如果INSERT语句中ID_Gender的值已经存在与TableFOREIGN,那么这个Probe(探测器)会返回关联值。否则返回NULL。所以这个“断言”是检查TableForeign中的值,如果没有找到INSERT中传入的值,断言会返回一个异常。

如果ID_Genders的值为NULL,那么SQL Server不能返回异常,而是返回“0”并继续运行语句。如果运行上面的INSERT语句,SQL Server会返回异常,因为值为’X’,违反了check约束:

但是如果把X换成F再运行,还是会报错,因为违反了外键约束:

但是当把4换成NULL或1或2或3之后,再运行插入语句,就不会产生异常:

原文出处:http://blog.csdn.net/dba_huangzj/article/details/50261747

断言与子查询:


断言操作符同样可以用于检查子查询,对于标量子查询不能返回多个值,但是有时候写法和数据的变动会引发多值错误。此时断言扮演着校验标量子查询是否返回一个值的角色。

下面来看看这两个语句:

  1. INSERT INTO TableAssert(ID, Gender) VALUES((SELECT ID FROM TableAssert), 'F')
  2. INSERT INTO TableAssert(ID, Gender) VALUES((SELECT ID FROM TableAssert), 'F')

用上面的方法查看一下执行计划:

  1. SET SHOWPLAN_TEXT ON
  2. GO
  3. INSERT INTO TableAssert(ID,Gender) VALUES((SELECT ID FROM TableAssert), 'F')
  4. INSERT INTO TableAssert(ID,Gender) VALUES((SELECT ID FROM TableAssert), 'F')

观察语句大概可以知道发生什么情况,第一个insert会成功(除非你已经修改过里面的数据),因为VALUES中的SELECT部分只返回一个值,但是第二个INSERT由于VALUES中的SELECT有两个值(第一个INSERT加入的),所以会报错。

结果如下:

  1. |--Assert(WHERE:([Expr1013]))
  2. |--Compute Scalar(DEFINE:([Expr1013]=CASE WHEN[tempdb].[dbo].[TableAssert].[Gender]<>'F' AND[tempdb].[dbo].[TableAssert].[Gender]<>'M' THEN (0) ELSE NULL END))
  3. |--Table Insert(OBJECT:([tempdb].[dbo].[TableAssert]),SET:([tempdb].[dbo].[TableAssert].[ID] =[Expr1009],[tempdb].[dbo].[TableAssert].[Gender] =[Expr1010],[tempdb].[dbo].[TableAssert].[ID_Genders] = NULL))
  4. |--Top(TOP EXPRESSION:((1)))
  5. |--ComputeScalar(DEFINE:([Expr1009]=[Expr1012], [Expr1010]='F'))
  6. |--Nested Loops(LeftOuter Join)
  7. |--ConstantScan
  8. |--Assert(WHERE:(CASE WHEN [Expr1011]>(1) THEN (0) ELSE NULL END))
  9. |--StreamAggregate(DEFINE:([Expr1011]=Count(*),[Expr1012]=ANY([tempdb].[dbo].[TableAssert].[ID])))
  10. |--Table Scan(OBJECT:([tempdb].[dbo].[TableAssert]))

注意最内层的Assert:

可以看到SQL Server创建一个StreamAggregate(流汇聚,可从预估执行计划中看到其解释,后续会专门介绍)去计算子查询会返回多少数据,然后把这个值传递给断言用于检测。

作为已经商业化二十几年的产品,其核心(查询优化器)已经经过了很多年的积累和改进,高版本的SQL Server(如2008 R2及以上版本,这个没有绝对标准),会对语句和表结构的当前情况来判断是否需要使用“断言,Assert”操作符。比如:

  1. INSERT INTO TableAssert(ID, Gender) VALUES((SELECT ID FROM TableAssert WHERE ID = 1), 'F')
  2. INSERT INTO TableAssert(ID, Gender) VALUES((SELECT TOP 1 ID FROM TableAssert), 'F')

原文出处:http://blog.csdn.net/dba_huangzj/article/details/50261747

先不执行,开启估计执行计划再看图形化界面,可以看到如下结果:

因为优化器检测到第二个语句里面包含了TOP 1,仅返回一行数据,所以没有必要引入断言来检测。

总结:


到这里为止,对这个操作符的介绍已经完毕,下一篇会介绍串联操作符。对于这个断言操作符,我们需要知道它是用来“验证”某些条件,但是每个操作符的引入都必将带来一定的开销,可是这些操作符的引入又是必须的,因为需要它们完成一些任务。如果需要改进,不妨先看看它是用来检验什么,比如上面提到的子查询,可以通过使用TOP 1、添加唯一约束等方式来减少这种校验。但是所有改进都应该做充分的测试和论证。

原文出处:http://blog.csdn.net/dba_huangzj/article/details/50261747


SQL Server 执行计划操作符详解(1)——断言(Assert)的更多相关文章

  1. SQL Server 执行计划操作符详解(3)——计算标量(Compute Scalar)

    接上文:SQL Server 执行计划操作符详解(2)--串联(Concatenation ) 前言: 前面两篇文章介绍了关于串联(Concatenation)和断言(Assert)操作符,本文介绍第 ...

  2. SQL Server 执行计划操作符详解(2)——串联(Concatenation )

    本文接上文:SQL Server 执行计划操作符详解(1)--断言(Assert) 前言: 根据计划,本文开始讲述另外一个操作符串联(Concatenation),读者可以根据这个词(中英文均可)先幻 ...

  3. MySQL 执行计划explain详解

    MySQL 执行计划explain详解 2015-08-10 13:56:27 分类: MySQL explain命令是查看查询优化器如何决定执行查询的主要方法.这个功能有局限性,并不总会说出真相,但 ...

  4. SQL Server 执行计划缓存

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/内存池/缓冲区 概述 了解执行计划对数据库性能分析很重要,其中涉及到了语句性能分析与存储,这也是写这篇文章的目的,在了解执行计划之 ...

  5. sql server 执行计划(execution plan)介绍

    大纲:目的介绍sql server 中执行计划的大致使用,当遇到查询性能瓶颈时,可以发挥用处,而且带有比较详细的学习文档和计划,阅读者可以按照我计划进行,从而达到对执行计划一个比较系统的学习. 什么是 ...

  6. SQL Server 执行计划中的扫描方式举例说明

    SQL Server 执行计划中的扫描方式举例说明 原文地址:http://www.cnblogs.com/zihunqingxin/p/3201155.html 1.执行计划使用方式 选中需要执行的 ...

  7. SQL Server执行计划那些事儿(3)——书签查找

    接下来的文章是记录自己曾经的盲点,同时也透漏了自己的发展历程(可能发展也算不上,只能说是瞎混).当然,一些盲点也在工作和探究过程中慢慢有些眉目,现在也愿意发扬博客园的奉献精神,拿出来和大家分享一下. ...

  8. SQL Server执行计划那些事儿(2)——查找和扫描

    接下来的文章是记录自己曾经的盲点,同时也透漏了自己的发展历程(可能发展也算不上,只能说是瞎混).当然,一些盲点也在工作和探究过程中慢慢有些眉目,现在也愿意发扬博客园的奉献精神,拿出来和大家分享一下. ...

  9. 引用:初探Sql Server 执行计划及Sql查询优化

    原文:引用:初探Sql Server 执行计划及Sql查询优化 初探Sql Server 执行计划及Sql查询优化 收藏 MSSQL优化之————探索MSSQL执行计划 作者:no_mIss 最近总想 ...

随机推荐

  1. preg_replace引发的phpmyadmin(4.3.0-4.6.2)命令执行漏洞

    编辑器坏了 (:  今天看到这个phpmyadmin的代码执行,https://www.waitalone.cn/phpmyadmin-preg_replace-rce.html 记录一下:preg_ ...

  2. [POI 2006]OKR-Periods of Words

    Description 题库链接 定义 \(A\) 串为 \(B\) 串的循环串,当且仅当 \(A\) 是 \(B\) 的前缀(不包括 \(B\) 本身),且 \(B\) 为连续的 \(A\) 串拼接 ...

  3. [Codeforces]871D Paths

    失踪OJ回归. 毕竟这样的数论没做过几道,碰上一些具体的应用还是无所适从啊.小C还是借助这题大致摸索一下莫比乌斯函数吧. Description 有n个点,标号为1~n,为这n个点建一张无向图.两个点 ...

  4. hdu 3939(勾股+容斥)

    题意: 给定一个整数L(L<=1e12),计算(x,y,z)组的个数.其中x<y<z,x^2+y^2=z^2,gcd(x,y)==1,gcd(x,z)==1,gcd(y,z)==1. ...

  5. WINFORM中treeview 节点显示不全

    在设置treeview节点时,出现如下显示不全的问题: 这个问题是由于我们在treeview任务中编辑节点时设置的字体大于我们在treeview属性中设置frot字体导致的. 所以只要将treevie ...

  6. Angular中 build的时候遇到的错误--There are multiple modules with names that only differ in casing

    今天早上遇到一个Angular的编译的时候的错误 具体信息: There are multiple modules with names that only differ in casing.This ...

  7. salesforce lightning零基础学习(二) lightning 知识简单介绍----lightning事件驱动模型

    看此篇博客前或者后,看一下trailhead可以加深印象以及理解的更好:https://trailhead.salesforce.com/modules/lex_dev_lc_basics 做过cla ...

  8. openlayers3设置zoom不变

    设置maxZoom和minZoom一致,并去掉resolutions

  9. Angular5学习笔记 http请求

    在anular4更新到angular5后,有些模块也发生了有些变化,例如http模块. 首先在app.module.ts里面引入HttpClientModule import { HttpClient ...

  10. TortiseGit 添加SSH-Key

    TortoiseGit 使用扩展名为ppk的密钥,而不是ssh-keygen生成的rsa密钥.使用命令ssh-keygen -C "邮箱地址" -t rsa产生的密钥在Tortoi ...