DML操作符包括增删改查等操作方式。

insert into Person.Address
(AddressLine1,
AddressLine2,
City,
StateProvinceID,
PostalCode,
rowguid,
ModifiedDate)
values(
N'1313 Mockingbird Lane',
N'Basement',
N'Springfield',
79,
N'02134',
NEWID(),
GETDATE()
)

  

如此简单的INSERT语句,执行计划比前面的SELECT语句还要复杂,表中有自增列、外键约束和空间数据列。

出现新的操作符:常量扫描(Constant Scan),这个操作符会引入一个常量到查询中,由它创建了一列空行以便后面两个操作符有位置可以添加他们的输出。

这两个操作符一个是标量计算(Compute Scalar),调用getidentity函数,用于产生自增值和标量数据,当INSET失败时,可以通过这个值获取失败信息;另一个操作是标量(Scalar)操作,它会产生一个序列用于新的uniqueidentifier值(因为表中有NEWID()函数),同时会产生GETDATE()函数所需要的日期和时间,接下来就是聚集索引插入操作了,这个执行计划中最大开销的部分,然后进入Nested Loops中,由于有外键,所以需要额外关联一个表StateProvince获取与之关联的外键StateProvinceID,经过关联后产生一个新的表达式,用于被下一个操作符Assert检测,这个Assert操作是否存在特定的条件,如果限制条件不存在,就把符合条件的数据插入到Person.Address.StateProvinceID列,然后结束INSET操作。

2.UPDATE

update Person.Address set City='Munro',ModifiedDate=GETDATE()
where City='Monroe'

  

最右边是一个非聚集索引扫描,并不是什么高效的操作。

可以进行索引策略来提高性能。让优化器能快速定位City=‘Monroe’的那些数据。查出数据后,进入Top操作符。在Update语句中,TOP操作强制控制行数,但在本例中没有控制,因为UPDATE语句中没有用到Top条件。接下来是表假脱机(Talbe Spool)这个操作会把需要UPDATE的数据移到TempDB的隐藏临时对象中,这个操作有利于回滚操作。

然后进入一系列的标量计算操作符中。在这里主要用于评估表达式和计算表,比如GETDATE()函数生成的值。

最终进入UPDATE语句的核心步骤:聚集索引更新。由于表上有聚集索引,而聚集索引实际包含整个表,所以UPDATE操作会引起聚集索引的更新,最后一个就是标识UPDATE操作完成的操作符。

本例提升性能最重要的方式就是降低开销最大的非聚集索引扫描操作消耗的开销,可以通过调整列上的索引来提升。

3.DELETE

begin tran
delete from Person.EmailAddress where BusinessEntityID=42
rollback tran

  

由于没有聚集索引,因此只需要把聚集索引的键删除即可。

二:复杂查询

包含存储过程、临时表、表变量、MERGE语句等的执行计划。

1.存储过程

create procedure Sales.spTaxRateByState
@CountryRegionCode nvarchar(3)
as
set nocount on;
select st.SalesTaxRateID,
st.Name,
st.TaxRate,
st.TaxType,
sp.Name as StateName
from Sales.SalesTaxRate st
join Person.StateProvince sp on st.StateProvinceID =sp.StateProvinceID
where sp.CountryRegionCode =@CountryRegionCode
order by StateName

  

从右到左分析,第一个操作符是聚集索引扫描,用于获取Tax Rate的数据,这个可以从tooltips上查看。接下来通过参数查找Person.StateProvince上的表符号[sp].CountryRegionCode=@CountryRegionCode条件的,也就是调用过程中实际传入的数据sp.CountryRegionCode='US',可以看出由于表Person.StateProvince上有筛选条件,所以出现了聚集索引查找,而表SalesTaxRate上没有筛选条件,所以只能进行聚集索引扫描

然后以StateProvince表为inner set,SalesTaxRate为outer set进行Nested Loops关联,由于两表的数据量较少,选择了Nested Loops关联更为高效。

关联之后就是排序操作,排序是由于StateProvince.Name导致的,代码中的Order By使用的是别名,而真正的排序是基于基础表的。这个表的聚集索引在StateProvinceID,所以当使用Name排序时,优化器会引入一个Sort操作符,开销很大。达到了51%,要优化,要在Sort上做文章,比如调整聚集索引,检查是否有必要排序等。

2.子查询

select p.Name,p.ProductNumber,ph.ListPrice
from Production.Product p
inner join Production.ProductListPriceHistory ph on
p.ProductID=ph.ProductID
where ph.StartDate=(
select top(1) ph2.StartDate
from Production.ProductListPriceHistory ph2
where ph2.ProductID=p.ProductID
order by ph2.StartDate desc
);

  

两个表Product和ProductListPriceHistory都进行了Merge Join,他们均有聚集索引,并且数据量相对前面来说比较大,所以优化器决定使用合并关联算法。然后对子查询进行Nested Loops关联,因为只需要取每个结果集的最近日期的数据,所以数据量相对较小,

本例中出现了非多对多的Merge join,从tooltips中的框线中看出,如果出现多对多的情况,优化器会创建一个Worktable的空间,这个空间位于TempDB,会导致额外的开销。

从filter操作符中可以看出,实现了对TOP 1 的处理,这个操作把两个数据集相等的数据进行对比,然后只取时间最大的那个。这个例子中最主要的地方就是进行了只查询的部分。在执行计划中这部分的操作演变成一个筛选操作,把符合where条件的数据返回。

3.使用APPLY的派生表

我们可以在SELECT过程中对创建的虚拟表进行数据操作,这种虚拟表叫作派生表。可以使用APPLY操作符来产生派生表,有CROSS APPLY和OUTER APPLY两种。

select p.Name,p.ProductNumber,ph.ListPrice from Production.Product p
cross apply (select TOP(1) ph2.ProductID,ph2.ListPrice from Production.ProductListPriceHistory ph2
where ph2.ProductID=p.ProductID order by ph2.StartDate desc) ph;

  

通过APPLY,先对派生表中每行进行WHERE条件及其他条件的应用,产生的结果集和Product表进行聚集索引扫面后,得到的数据进行Nested Loops 关联,最后返回结果。

通过查看I/O情况,子查询开销更低。

如果加入where 条件 where p.ProductID='839'

当加入WHERE条件之后,派生表的数据集迅速降低,在JOIN的过程中更加快速和高效。

4.通用表表达式

它是SQL语句中的一个临时结果集

ALTER PROCEDURE [dbo].[uspGetManagerEmployees]
@BusinessEntityID [int]
AS
BEGIN
SET NOCOUNT ON;
WITH [EMP_cte] ( [BusinessEntityID], [OrganizationNode], [FirstName], [LastName], [RecursionLevel] )
-- CTE name and columns
AS ( SELECT e.[BusinessEntityID] ,
e.[OrganizationNode] ,
p.[FirstName] ,
p.[LastName] ,
0 -- Get the initial list of Employees
-- for Manager n
FROM [HumanResources].[Employee] e
INNER JOIN [Person].[Person] p ON p.[BusinessEntityID] = e.[BusinessEntityID]
WHERE e.[BusinessEntityID] = @BusinessEntityID
UNION ALL
SELECT e.[BusinessEntityID] ,
e.[OrganizationNode] ,
p.[FirstName] ,
p.[LastName] ,
[RecursionLevel] + 1 -- Join recursive
-- member to anchor
FROM [HumanResources].[Employee] e
INNER JOIN [EMP_cte] ON e.[OrganizationNode].GetAncestor(1) = [EMP_cte].[OrganizationNode]
--GetAncestor 返回表示this的第n个祖先的 hierarchyid。
INNER JOIN [Person].[Person] p ON p.[BusinessEntityID] = e.[BusinessEntityID]
)
SELECT [EMP_cte].[RecursionLevel] ,
[EMP_cte].[OrganizationNode].ToString() AS [OrganizationNode] ,
p.[FirstName] AS 'ManagerFirstName' ,
p.[LastName] AS 'ManagerLastName' ,
[EMP_cte].[BusinessEntityID] ,
[EMP_cte].[FirstName] ,
[EMP_cte].[LastName] -- Outer select from the CTE
FROM [EMP_cte]
INNER JOIN [HumanResources].[Employee] e ON [EMP_cte].[OrganizationNode].GetAncestor(1) = e.[OrganizationNode]
INNER JOIN [Person].[Person] p ON p.[BusinessEntityID] = e.[BusinessEntityID]
ORDER BY [RecursionLevel] ,
[EMP_cte].[OrganizationNode].ToString()
OPTION ( MAXRECURSION 25 ) --指定应在整个查询中使用所指定的查询提示
END;

  XML格式的执行计划

        set statistics xml ON;
exec dbo.uspGetEmployeeManagers @businessEntityID=9;
set statistics xml off

  单击XML链接就可以看到图形化执行计划。

最右的执行计划:(从右到左)

进行一联串的Inner Join Join 嵌套的循环之后,进入Concatenation操作,这个操作是针对代码中的UNION ALL 的,会把数据合成一个数据集。

执行计划中的假表脱机(Table Spool 操作符)。这个操作符可以让数据集被多次循环,而不需要在每次需要时都临时创建了。另外这个和索引假脱机类似,他们都是把数据存放在TempDb的隐藏临时表中,其实就是对CTE的UNION ALL操作进行重绕(可以理解为递归操作)

在Concatenation之后,就是索引假脱机(Index Spool)这个操作会把数据汇总到TempDb中一个叫Worktable的地方,然后与查询中返回的地方就行索引关联,以便实现递归操作。

5.视图

视图的本质就是一个查询,有两种视图:标准视图和索引视图

(1)标准视图

select * from Sales.vIndividualCustomer where BusinessEntityID=8743

  

在标准视图中,SQL Server会认为这个视图仅仅是一个名称,优化器会忽略视图这个对象,取而代之的是直接处理底层表。

(2)索引视图

这类视图也叫物化视图,本质上就是视图加一个聚集索引,这种视图实际上等价于在数据库中创建了一个物理表,很多时候,索引视图能极大的提高性能,创建索引视图也是一个开销比较大的操作,只不过只需要创建一次。

select * from Person.vStateProvinceCountryRegion

没有where 条件,进行的是聚集索引扫描

  

SQL Server INSET/UPDATE/DELETE的执行计划的更多相关文章

  1. SQL Server 性能调优 之执行计划(Execution Plan)调优

    SQL Server 存在三种 Join 策略:Hash Join,Merge Join,Nested Loop Join. Hash Join:用来处理没有排过序/没有索引的数据,它在内存中把 Jo ...

  2. SQL Server如何查看存储过程的执行计划

    有时候,我们需要查看存储过程的执行计划,那么我们有什么方式获取存储过程的历史执行计划或当前的执行计划呢? 下面总结一下获取存储过程的执行计划的方法. 1:我们可以通过下面脚本查看存储过程的执行计划,但 ...

  3. SQL Server中UPDATE和DELETE语句结合INNER/LEFT/RIGHT/FULL JOIN的用法

    在SQL Server中,UPDATE和DELETE语句是可以结合INNER/LEFT/RIGHT/FULL JOIN来使用的. 我们首先在数据库中新建两张表: [T_A] CREATE TABLE ...

  4. Sql Server 带参数的存储过程执行方法

    Sql Server 带参数的存储过程执行方法 Visual C# 动态操作 SQL Server 数据库实例教程(4):带参数的存储过程执行方法 上一篇文章介绍了带参数的SQL语句执行方法和不带参数 ...

  5. ECSHOP后台SQL查询提示错误 this sql May contain UPDATE,DELETE,TRUNCATE,ALTER,DROP,FLUSH,INSERT

    一).首先说一下错误现象:市面上流行的绝大部分ECSHOP模板,安装的时候都需要执行一段或几段SQL语句来修改数据结构或者初始化一些数据.大多数ECSHOP管理员为了省事,都会通过 “ECSHOP后台 ...

  6. SQL点滴27—性能分析之执行计划

    原文:SQL点滴27-性能分析之执行计划 一直想找一些关于SQL语句性能调试的权威参考,但是有参考未必就能够做好调试的工作.我深信实践中得到的经验是最珍贵的,书本知识只是一个引导.本篇来源于<I ...

  7. 浅析SqlServer简单参数化模式下对sql语句自动参数化处理以及执行计划重用

    我们知道,SqlServer执行sql语句的时候,有一步是对sql进行编译以生成执行计划, 在生成执行计划之前会去缓存中查找执行计划 如果执行计划缓存中有对应的执行计划缓存,那么SqlServer就会 ...

  8. SQL SERVER的update select语句的写法

    需求: 要根据表A的数据来更新表B的某些字段,A和B要进行条件关联. 常规做法可能写个子查询 简单写法是用SQL Server的update select语法 update T_STOCK_INFO ...

  9. 利用SQL Server 2008 R2创建自动备份计划

    本文主要利用SQL Server 2008 R2自带的"维护计划"创建一个自动备份数据的任务. 首先,启动 Sql Management studio,确保"SQL Se ...

随机推荐

  1. python 之路,Day 1 python基础 之 课后随笔

    首先是抱着被忽悠的心态,购买了老男孩的什么什么什么(你懂得!!),开始了一周一堂课时的听,然后就是做,自己的博客,首先附上整理的内容吧. 1day .... 一. Hell world 程序 在lin ...

  2. ES--05

    第四十一讲!分词器内部组成 内置分词器 课程大纲 1.什么是分词器 切分词语,normalization(提升recall召回率) 给你一段句子,然后将这段句子拆分成一个一个的单个的单词,同时对每个单 ...

  3. 转载:Practical UML™: A Hands-On Introduction for Developers

    原文:http://edn.embarcadero.com/article/31863 By: Randy Miller Abstract: This tutorial provides a quic ...

  4. django-form介绍

    Django form表单   目录 普通方式手写注册功能 views.py login.html 使用form组件实现注册功能 views.py login2.html 常用字段与插件 initia ...

  5. python---控制台输出带颜色的文字方法

    控制台的展示效果有限,并不能像前端一样炫酷,只能做一些简单的设置,不过站在可读性的角度来看,已经好很多了. 书写格式: ##格式: 设置颜色开始:\033[显示方式;前景色;背景色m ##说明: 前景 ...

  6. Ex 2_22 两个有序列表合并后的第k小元素..._第四次作业

    package org.xiu68.ch02; public class Ex2_22 { public static void main(String[] args) { // TODO Auto- ...

  7. 【原创】大叔经验分享(32)docker挂载文件修改生效

    docker经常需要挂载文件到容器中,比如启动nginx # docker run -d --name test_nginx -v /tmp/nginx.conf:/etc/nginx/nginx.c ...

  8. 关于socket.io获取客户端真实IP地址

    1 前言 由于使用了CDN加速,导致了socket.handshake.address拿到值都是服务器的,而没有使用CDN加速时,可以拿到客户端真实IP. 2 代码 if(socket.handsha ...

  9. Java学习——面向对象【3】

    1. 继承  java的类是单继承(一个子类只能继承一个父类),不能多继承(多个父类):A继承自B,A也继承自C,可以多重继承,就是A继承自B,B继承自C(A->B->C), 所有的类都继 ...

  10. Confluence 6 安全相关问题提交链接

    找到和报告安全漏洞 Atlassian 有关安全漏洞的报告细节,请参考如何报告一个安全问题(How to Report a Security Issue)链接. 发布 Confluence 安全公共 ...