前一段时间,给一位朋友公司做咨询,看到他们的很多的存储过程都存在动态sql语句执行,sp_executesql,即使在没有动态表名,动态字段名的情况下仍然使用sp_executesql,这个做法是不太明智的,会存在一些性能方面的问题。
先说说什么场景使用这个系统存储过程吧,sp_executesql,是sql server动态执行一段可以带有参数(内参,外参)的语句文本的系统存储过程,传入sp_executesql 的参数会以参数的形式传递,不会是以拼凑sql的形式传递,所以能够在不得不拼接sql语句的情景下使用以防止sql注入。不得不拼接sql的情景包括 传递in内参数,动态决定表列,列名,还有就是like,为防止sql注入,也不得不拼接sql。按理来说这是一个非常好的存储过程,但是,由于他本身的限制,会对查询性能有很大的影响,下面我举个例子。

使用northwind数据库,
执行:

select * from orders where customerid = 'SAVEA';

执行:

select * from orders where customerid = 'CENTC';

这两个语句的唯一不同就是客户号不一样,一个在订单表内有31个重复值,一个没有重复值。
然后咱们再来对比当这个语句放在了一个动态执行的sql语句内部的情况如何。

创建如下存储过程:

USE [Northwind]
GO
/****** 对象: StoredProcedure [dbo].[testexecutesql] 脚本日期: 10/15/2009 20:09:45 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER proc [dbo].[testexecutesql](@customerid nchar(5))
as
begin exec sp_executesql N'select * from orders where customerid = @cid ',
N'@cid as nchar(5)',@cid = @customerid ;
end

然后执行这个存储过程:

exec testexecutesql 'SAVEA';

其执行计划如下图,是个聚集索引扫描:

表 'Orders'。扫描计数 1,逻辑读取 22 次,物理读取 0 次,预读 0 次,lob 逻辑读取 0 次,lob 物理读取 0 次,lob 预读 0 次。

使用聚集索引扫描是个很明智的选择,咱们可以来看看customerid上的非聚集索引的统计信息,orders表共830行,其中客户'SAVEA'就有31个订单,所以优化器选择使用聚集索引扫描而不是嵌套循环的book mark look up。
然后咱们再来执行一个:

exec testexecutesql 'CENTC';

区别仅仅是传入的customerid参数不一样,再看看执行计划,仍然是一样,io也是一样,就是返回的行数只有一行,按理来说,只返回一行,优化器应该会选择使用非聚集索引,嵌套查找数据,但是优化器却没有好好利用customerid上的统计信息,仍然使用了聚集索引扫描,为什们?难道是索引上的统计信息不及时吗?不,在手动使用fullscan后的统计信息仍然是一样的查询计划,为什么呢?
因为sp_executesql本身就是一个存储过程,他执行动态语句的参数是不会被利用上的,所以当第一次编译的时候产生的计划,存储过程testexecutesql 是无法嗅探到的,即无法去引用customerid上的统计信息来做查询计划参考的,所以第一次编译的查询计划是聚集索引扫描就是扫描,即使第二次执行的时候应该是查找。
如何才能改变这一现状呢?
可以使用提示符,recompile强制让存储过程在执行的时候重新编译,来获得最好的执行计划,不过这也是有代价的,就是每次都需要编译,不过相比那些被浪费掉的IO,对一些大表的性能低下的查询计划还是很值得的。于是,我们把存储过程改写如下:

USE [Northwind]
GO
/****** 对象: StoredProcedure [dbo].[testexecutesql] 脚本日期: 10/15/2009 20:09:45 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER proc [dbo].[testexecutesql](@customerid nchar(5))
as
begin exec sp_executesql N'select * from orders where customerid = @cid option(recompile) ',
N'@cid as nchar(5)',@cid = @customerid ; --也可以用sp_executesql N'select * from orders where customerid = @cid',N'@cid as nchar(5)',@cid = @customerid with recompile end

这样再次执行

exec testexecutesql 'CENTC';
exec testexecutesql 'SAVEA';

都能获得一个最优的查询计划。

sql server能够支持语句级的重编译,自动嗅探重编译环境,阀值,使得绝大部分情况下能够很好的利用编译后的查询计划,提高数据库整体性能。

所以我们知道了sp_executesql这个系统存储过程默认情况下会保存所执行动态sql语句的执行计划,以便复用。但是用exec(@sql)这种方式执行动态sql,其执行sql语句的执行计划是不会被保存的,每次执行exec(@sql)都是生成一个新的执行计划,所以从原则上来说通过sp_executesql执行动态sql语句的确要比用exec(@sql)性能更高,但是正因如此有时候sp_executesql也会带来一些不必要的麻烦,就如本文所述。

exec(@sql)和sp_executesql @sql有什么不同?

exec(@sql)是每次生成执行计划
sp_executesql 在后面加上with recompile 应该也是每次生成执行计划
这样sp_executesql 的作用仅限在了强制参数话,而无性能上的提升了。

所以这个with recompile的取与用,还需要实际环境做判断。如果加上option(recompile)选项,那么就sp_executesql的执行计划就没有得到充分利用,也就是和EXEC(sql)一样了,只不过稍微比EXEC安全,不会被SQL注入。EXEC和sp_executersql的最大的区别就是sp_executesql能够充分利用执行计划,当然还有sp_executesql支持带参数返回等。。

原文链接

sp_executesql的执行计划会被重用(转载)的更多相关文章

  1. Sql server在使用sp_executesql @sql执行文本sql时,报错: Could not find database ID 16, name '16'. The database may be offline. Wait a few minutes and try again.

    最近在公司项目中使用exec sp_executesql @sql执行一段文本sql的时候老是报错: Could not find database ID 16, name '16'. The dat ...

  2. SQL Server中参数化SQL写法遇到parameter sniff ,导致不合理执行计划重用的一种解决方案

    parameter sniff问题是重用其他参数生成的执行计划,导致当前参数采用该执行计划非最优化的现象.想必熟悉数据的同学都应该知道,产生parameter sniff最典型的问题就是使用了参数化的 ...

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

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

  4. SQL Server 利用Profiler观察执行计划是否重用时SP:Cachemiss,SP:CacheInsert以及SP:CacheHit的含义

    本文出处:http://www.cnblogs.com/wy123/p/6913055.html 执行计划的缓存与重用 在通过SQL Profile观察一个SQL语句或者存储过程是否有可用的缓存执行计 ...

  5. 记一次SqlServer大表查询语句优化和执行计划分析

    数据库: sqlserver2008r2 表: device_data 数据量:2000w行左右 表结构 CREATE TABLE [dbo].[device_data]( [Id] [int] ID ...

  6. 使用exec和sp_executesql动态执行SQL语句(转载)

    当需要根据外部输入的参数来决定要执行的SQL语句时,常常需要动态来构造SQL查询语句,个人觉得用得比较多的地方就是分页存储过程和执行搜索查询的SQL语句.一个比较通用的分页存储过程,可能需要传入表名, ...

  7. 【转载】SQL执行计划

    要理解执行计划,怎么也得先理解,那各种各样的名词吧.鉴于自己还不是很了解.本文打算作为只写懂的,不懂的懂了才写. 在开头要先说明,第一次看执行计划要注意,SQL Server的执行计划是从右向左看的. ...

  8. MongoDB干货系列2-MongoDB执行计划分析详解(2)(转载)

    写在之前的话 作为近年最为火热的文档型数据库,MongoDB受到了越来越多人的关注,但是由于国内的MongoDB相关技术分享屈指可数,不少朋友向我抱怨无从下手. <MongoDB干货系列> ...

  9. oracle执行计划(转载)

    转载自 https://www.cnblogs.com/Dreamer-1/p/6076440.html 一:什么是Oracle执行计划? 执行计划是一条查询语句在Oracle中的执行过程或访问路径的 ...

随机推荐

  1. linux pipe

    1. 函数说明 pipe(建立管道): 1) 头文件 #include<unistd.h> 2) 定义函数: int pipe(int filedes[2]); 3) 函数说明: pipe ...

  2. Arbitrage---poj2240(floyd)

    题目链接:http://poj.org/problem?id=2240 题意:有n个国家的,有m个关系,每个关系的格式是:A B C表示1单位的A国货币可以换B单位C国货币:求是否存在一种方法使得货币 ...

  3. Effective Project Communications

    I was recently invited to speak at a conference in Singapore on Effective Project Communications. I' ...

  4. SQLServer DMV Query

    1.DMV Query to identify all active SQL Server Sessions The query below identifies all currently acti ...

  5. 详解js变量、作用域及内存

    详解js变量.作用域及内存 来源:伯乐在线 作者:trigkit4       原文出处: trigkit4    基本类型值有:undefined,NUll,Boolean,Number和Strin ...

  6. mv命令(转)

    mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录. 1.命令格式: mv [选项] 源文件或目 ...

  7. hibernate一对多注解

    package net.zmcheng.model; import java.util.HashSet;import java.util.Set; import javax.persistence.C ...

  8. PHP登陆Session验证

    关键字:PHP Session 登陆 验证 本文地址:http://www.cnblogs.com/txw1958/p/php-login-check-session.html 首先,在MySQL数据 ...

  9. 10月26日 奥威Power-BI基于微软示例库(MSOLAP)快速制作管理驾驶舱 腾讯课堂开课啦

    本次课是基于olap数据源的案例实操课,以微软olap示例库Adventure Works为数据基础.        AdventureWorks示例数据库为一家虚拟公司的数据,公司背景为大型跨国生产 ...

  10. UIPickerView详解

    一.UIPickerView 1.UIPickerView的常见属性 // 数据源(用来告诉UIPickerView有多少列多少行) @property(nonatomic,assign) id< ...