如何用参数化SQL语句污染你的计划缓存
你的SQL语句的参数化总是个好想法。使用参数化SQL语句你不会污染你的计划缓存——错!!!在这篇文章里我想向你展示下用参数化SQL语句就可以污染你的计划缓存,这是非常简单的!
ADO.NET-AddWithValue
ADO.NET是实现像SQL Server关系数据库数据访问的.NET框架的组成——有一些严重的副作用。不要误解我——只要你正确使用,ADO.NET一直很棒。你马上就会看到,它很容易被错误使用。我们来看下面实现SQL语句执行的C#代码。
for (int i = ; i <= ; i++)
{
val += i.ToString(); cmd = new SqlCommand(
"SELECT * FROM Sales.SalesOrderDetail WHERE CarrierTrackingNumber = @CarrierTrackingNumber",
cnn);
cmd.Parameters.AddWithValue("@CarrierTrackingNumber", val);
SqlDataReader reader = cmd.ExecuteReader();
reader.Close();
}
我们是聪明的开发者,因此SQL语句本身被参数化,因为ADO.NET框架是地球上最棒的框架,我们使用System.Data.SqlClient.SqlParameterCollection类的AddWithValue方法来提供实际的参数值。我在WHLIE循环里运行那个SQL语句100次,总用不同长度赋予参数值。在Sales.SalesOrderDetail表里CarrierTrackingNumber列定义为NVARCHAR(25)。因此我们可以在基于我们提供的不同字符长度上有上至25个不同数据类型的参数。现在让我们检查下我们SQL语句执行后的计划缓存。
SELECT
st.text,
cp.*
FROM sys.dm_exec_cached_plans cp
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st
GO
现在事情变得有点疯狂:在计划缓存里我们存储了100个不同的执行计划!

对于每个可能的数据类型参数都有1个执行计划——即使当数据类型是NVACHAR(25)。AddWithValue方法非常,非常邪恶:基于你提供的参数值派生出数据类型。永远不要使用它!
ADO.NET – SqlDbType.VarChar
因为从我们的错误中我们学到了,现在我们知道ADO.NET的AddWithValue方法的副作用——我们不再用它。现在让我们重写我们的C#程序代码,如下所示定义一个显示的参数数据类型:
for (int i = ; i <= ; i++)
{
val += i.ToString(); cmd = new SqlCommand(
"SELECT * FROM Sales.SalesOrderDetail WHERE CarrierTrackingNumber = @CarrierTrackingNumber",
cnn);
cmd.Parameters.Add(new SqlParameter("@CarrierTrackingNumber", SqlDbType.VarChar));
cmd.Parameters["@CarrierTrackingNumber"].Value = val;
SqlDataReader reader = cmd.ExecuteReader();
reader.Close();
}
从代码里你可以看到,ADO.NET现在不能派生参数数据类型了,因为我们已经指定了SqlDbType.Varchar数据类型。让我们再次执行这个SQL语句100次并再次检查下计划缓存:

没有啥改变。问题还是一样:在计划缓存里我们还有100个不一样的的执行计划。现在的问题是ADO.NET只强制数据类型(SqlDbType.VarChar),但不是数据类型的"长度"。有100个不同的长度在计划缓存里你就有100个不同的执行计划。
如果你在你的ADO.NET代码里显式指定参数数据类型,你也要指定它的长度!现在我们来看下一些修正的C#代码。
for (int i = ; i <= ; i++)
{
val += i.ToString(); cmd = new SqlCommand(
"SELECT * FROM Sales.SalesOrderDetail WHERE CarrierTrackingNumber = @CarrierTrackingNumber",
cnn);
cmd.Parameters.Add(new SqlParameter("@CarrierTrackingNumber", SqlDbType.VarChar, ));
cmd.Parameters["@CarrierTrackingNumber"].Value = val;
SqlDataReader reader = cmd.ExecuteReader();
reader.Close();
}
这次我也指定了数据类型的长度——这里是100,现在当我们再次执行SQL语句100次时,最后我们在计划缓存里以1个执行计划且重用了100次来完美收工。这是从SQL Server角度的最终目标。

小结
寓意:ADO.NET是个很棒的数据访问框架,它提供你有用的功能(例如AddWithValue方法),当从SQL Server角度来说你真的要考虑下你在做什么。当你使用参数化SQL语句时,你要尽量显式:你必须地冠以参数值的实际数据类型,还有你想要的获得数据类型长度。
感谢关注!
参考文章:
如何用参数化SQL语句污染你的计划缓存的更多相关文章
- 在ADO.NET中使用参数化SQL语句访问不同数据库时的差异
在ADO.NET中经常需要跟各种数据库打交道,在不实用存储过程的情况下,使用参数化SQL语句一定程度上可以防止SQL注入,同时对一些较难赋值的字段(如在SQL Server中Image字段,在Orac ...
- 参数化SQL语句
避免SQL注入的方法有两种:一是所有的SQL语句都存放在存储过程中,这样不但可以避免SQL注入,还能提高一些性能,并且存储过程可以由专门的数据库管理员(DBA)编写和集中管理,不过这种做法有时候针对相 ...
- SQL Server参数化SQL语句中的like和in查询的语法(C#)
sql语句进行 like和in 参数化,按照正常的方式是无法实现的 我们一般的思维是: Like参数化查询:string sqlstmt = "select * from users whe ...
- 查看SQL语句的真实执行计划
DBMS_XPLAN包中display_cursor函数不同于display函数,display_cursor用于显示SQL语句的真实的执行计划,在大多数情况下,显示真实的执行计划有助于更好的分析SQ ...
- MySQL的EXPLAIN命令用于SQL语句的查询执行计划
MySQL的EXPLAIN命令用于SQL语句的查询执行计划(QEP).这条命令的输出结果能够让我们了解MySQL 优化器是如何执行SQL 语句的.这条命令并没有提供任何调整建议,但它能够提供重要的信息 ...
- 谈一谈SQL Server中的执行计划缓存(上)
简介 我们平时所写的SQL语句本质只是获取数据的逻辑,而不是获取数据的物理路径.当我们写的SQL语句传到SQL Server的时候,查询分析器会将语句依次进行解析(Parse).绑定(Bind).查询 ...
- 浅析SQL Server中的执行计划缓存(上)
简介 我们平时所写的SQL语句本质只是获取数据的逻辑,而不是获取数据的物理路径.当我们写的SQL语句传到SQL Server的时候,查询分析器会将语句依次进行解析(Parse).绑定(Bind).查询 ...
- 利用IFormattable接口自动参数化Sql语句
提要 string.Format("{0},{1}",a,b)的用法大家都不陌生了,在很多项目中都会发现很多sql语句存在这样拼接的问题,这种做法很多"懒"程序 ...
- C# 参数化SQL语句中的like和in
在写项目的时候遇到一个问题,sql 语句进行 like in 参数化,按照正常的方式是无法实现的我们一般的思维是: Like 参数:string strSql = "select * fro ...
随机推荐
- 【转】Android类动态加载技术
http://www.blogjava.net/zh-weir/archive/2011/10/29/362294.html Android应用开发在一般情况下,常规的开发方式和代码架构就能满足我们的 ...
- Understanding mysql max_connect_errors
来自:http://mysqlblog.fivefarmers.com/2013/08/08/understanding-max_connect_errors/ Perhaps like many u ...
- SVO实时全局光照:Sparse Voxel Octree based Global Illumination (SVO GI)
功能已实现,初步集成进来,暂未进行重度优化.但GI的效果已很明显.这里特地给出了开启实时GI前后的效果对比,比对场景如下:1.只有直接光照(方向光源)的场景.2在直接光照(方向光源)基础上开启了实时G ...
- [Aaronyang] 写给自己的WPF4.5 笔记23 [3d交互与动画 4/4]
效果图预览: 1. 3d中的命中测试 我新建了一个空的窗口,用zam做了一个长方体,深度很小.然后导出xaml <Viewport3D x:Name="ZAM3DViewport3D& ...
- Fiddler高级技巧 - 映射路径到本地文件夹
适用场景: 你是前端开发人员,要开发一个小模块,需要用到线上的环境(账号.数据.跨域等),但你又没有权限往线上传文件 你是移动测试人员,需要将一组接口的返回结果替换为另一组,最简单的办法就是使用Fid ...
- THinkphp开启静态(动态)缓存的用法
<?php return array( //开启静态缓存 'HTML_CACHE_ON' => true, 'HTML_CACHE_RULES' => array( 'News:in ...
- Android Studio 快捷键 for mac
Action Mac OS Win/Linux 打开文件 Cmd + shift + O 打开Class文件 Cmd + O 覆写方法 Ctrl + O 生成方法(重写构造.setter ...
- TargetProcess 中更改了域名如何修改里面的附件地址
在默认情况,TP 安装的目录是默认的网站(IIS)的targetprocess2下面,但是如果想采用 tp.targetprocess.cn 这种方式直接访问,则需要做一些设置. 基本要点如下: 1. ...
- ssh框架搭建错误集合
1,把jsp放入到WEB-INF/view目录下,struts2.xml配置<result name="success">/WEB-INF/view/home.jsp& ...
- BlazeMeter发布chrome扩展插件,支持JMeter脚本创建
BlazeMeter发布chrome扩展插件,支持JMeter脚本创建http://www.automationqa.com/forum.php?mod=viewthread&tid=3898 ...