原文地址:

http://www.cnblogs.com/lzrabbit/archive/2012/04/22/2465313.html


除了校验参数内容,过滤长度和sql关键字。

解决in条件拼接字符串

comm.CommandText = "select * from Users(nolock) where UserID in (@UserID1,@UserId2,@UserID3,@UserID4)";
comm.Parameters.AddRange(
new SqlParameter[]{
new SqlParameter("@UserID1", SqlDbType.Int) { Value = 1},
new SqlParameter("@UserID2", SqlDbType.Int) { Value = 2},
new SqlParameter("@UserID3", SqlDbType.Int) { Value = 3},
new SqlParameter("@UserID4", SqlDbType.Int) { Value = 4}
});

文章导读

拼SQL实现where in查询

使用CHARINDEX或like实现where in 参数化

使用exec动态执行SQl实现where in 参数化

为每一个参数生成一个参数实现where in 参数化

使用临时表实现where in 参数化

like参数化查询

xml和DataTable传参 

身为一名小小的程序猿,在日常开发中不可以避免的要和where in和like打交道,在大多数情况下我们传的参数不多简单做下单引号、敏感字符转义之后就直接拼进了SQL,执行查询,搞定。若有一天你不可避免的需要提高SQL的查询性能,需要一次性where in 几百、上千、甚至上万条数据时,参数化查询将是必然进行的选择。然而如何实现where in和like的参数化查询,是个让不少人头疼的问题。

where in 的参数化查询实现

首先说一下我们常用的办法,直接拼SQL实现,一般情况下都能满足需要

string userIds = "1,2,3,4";
using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = string.Format("select * from Users(nolock) where UserID in({0})", userIds);
comm.ExecuteNonQuery();
}

需要参数化查询时进行的尝试,很显然如下这样执行SQL会报错错误

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "select * from Users(nolock) where UserID in(@UserID)";
comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.VarChar, -1) { Value = "1,2,3,4" });
comm.ExecuteNonQuery();
}

很显然这样会报错误:在将 varchar 值 '1,2,3,4' 转换成数据类型 int 时失败,因为参数类型为字符串,where in时会把@UserID当做一个字符串来处理,相当于实际执行了如下语句

select * from Users(nolock) where UserID in('1,2,3,4')

若执行的语句为字符串类型的,SQL执行不会报错,当然也不会查询出任何结果

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
comm.CommandText = "select * from Users(nolock) where UserName in(@UserName)";
comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar, -1) { Value = "'john','dudu','rabbit'" });
comm.ExecuteNonQuery();
}

这样不会抱任何错误,也查不出想要的结果,因为这个@UserName被当做一个字符串来处理,实际相当于执行如下语句

select * from Users(nolock) where UserName in('''john'',''dudu'',''rabbit''')

由此相信大家对于为何简单的where in 传参无法得到正确的结果知道为什么了吧,下面我们来看一看如何实现正确的参数化执行where in,为了真正实现参数化where in 传参,很多淫才想到了各种替代方案

方案1,使用CHARINDEX或like 方法实现参数化查询,毫无疑问,这种方法成功了,而且成功的复用了查询计划,但同时也彻底的让查询索引失效(在此不探讨索引话题),造成的后果是全表扫描,如果表里数据量很大,百万级、千万级甚至更多,这样的写法将造成灾难性后果;如果数据量比较小、只想借助参数化实现防止SQL注入的话这样写也无可厚非,还是得看具体需求。(不推荐)

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
//使用CHARINDEX,实现参数化查询,可以复用查询计划,同时会使索引失效
comm.CommandText = "select * from Users(nolock) where CHARINDEX(','+ltrim(str(UserID))+',',','+@UserID+',')>0";
comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.VarChar, -1) { Value = "1,2,3,4" });
comm.ExecuteNonQuery();
} using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
//使用like,实现参数化查询,可以复用查询计划,同时会使索引失效
comm.CommandText = "select * from Users(nolock) where ','+@UserID+',' like '%,'+ltrim(str(UserID))+',%' ";
comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.VarChar, -1) { Value = "1,2,3,4" });
comm.ExecuteNonQuery();
}

方案2 使用exec动态执行SQL,这样的写法毫无疑问是很成功的,而且代码也比较优雅,也起到了防止SQL注入的作用,看上去很完美,不过这种写法和直接拼SQL执行没啥实质性的区别,查询计划没有得到复用,对于性能提升没任何帮助,颇有种脱了裤子放屁的感觉,但也不失为一种解决方案。(不推荐)

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
//使用exec动态执行SQL
  //实际执行的查询计划为(@UserID varchar(max))select * from Users(nolock) where UserID in (1,2,3,4)
  //不是预期的(@UserID varchar(max))exec('select * from Users(nolock) where UserID in ('+@UserID+')')
comm.CommandText = "exec('select * from Users(nolock) where UserID in ('+@UserID+')')";
comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.VarChar, -1) { Value = "1,2,3,4" });
comm.ExecuteNonQuery();
}

方案3 为where in的每一个参数生成一个参数,写法上比较麻烦些,传输的参数个数有限制,最多2100个,可以根据需要使用此方案(推荐)

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
//为每一条数据添加一个参数
comm.CommandText = "select * from Users(nolock) where UserID in (@UserID1,@UserId2,@UserID3,@UserID4)";
comm.Parameters.AddRange(
new SqlParameter[]{
new SqlParameter("@UserID1", SqlDbType.Int) { Value = 1},
new SqlParameter("@UserID2", SqlDbType.Int) { Value = 2},
new SqlParameter("@UserID3", SqlDbType.Int) { Value = 3},
new SqlParameter("@UserID4", SqlDbType.Int) { Value = 4}
}); comm.ExecuteNonQuery();
}

方案4 使用临时表实现(也可以使用表变量性能上可能会更加好些),写法实现上比较繁琐些,可以根据需要写个通用的where in临时表查询的方法,以供不时之需,个人比较推崇这种写法,能够使查询计划得到复用而且对索引也能有效的利用,不过由于需要创建临时表,会带来额外的IO开销,若查询频率很高,每次的数据不多时还是建议使用方案3,若查询数据条数较多,尤其是上千条甚至上万条时,强烈建议使用此方案,可以带来巨大的性能提升(强烈推荐)

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
string sql = @"
declare @Temp_Variable varchar(max)
create table #Temp_Table(Item varchar(max))
while(LEN(@Temp_Array) > 0)
begin
if(CHARINDEX(',',@Temp_Array) = 0)
begin
set @Temp_Variable = @Temp_Array
set @Temp_Array = ''
end
else
begin
set @Temp_Variable = LEFT(@Temp_Array,CHARINDEX(',',@Temp_Array)-1)
set @Temp_Array = RIGHT(@Temp_Array,LEN(@Temp_Array)-LEN(@Temp_Variable)-1)
end
insert into #Temp_Table(Item) values(@Temp_Variable)
end
select * from Users(nolock) where exists(select 1 from #Temp_Table(nolock) where #Temp_Table.Item=Users.UserID)
drop table #Temp_Table";
comm.CommandText = sql;
comm.Parameters.Add(new SqlParameter("@Temp_Array", SqlDbType.VarChar, -1) { Value = "1,2,3,4" });
comm.ExecuteNonQuery();
}

like参数化查询
like查询根据个人习惯将通配符写到参数值中或在SQL拼接都可,两种方法执行效果一样,在此不在详述

using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
//将 % 写到参数值中
comm.CommandText = "select * from Users(nolock) where UserName like @UserName";
comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar, 200) { Value = "rabbit%" });
comm.ExecuteNonQuery();
} using (SqlConnection conn = new SqlConnection(connectionString))
{
conn.Open();
SqlCommand comm = new SqlCommand();
comm.Connection = conn;
//SQL中拼接 %
comm.CommandText = "select * from Users(nolock) where UserName like @UserName+'%'";
comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar, 200) { Value = "rabbit%" });
comm.ExecuteNonQuery();
}

看到Tom.汤和蚊子额的评论 补充了下xml传参和tvp传参,并对6种方案做了个简单总结

Sql Server参数化查询之where in和like实现之xml和DataTable传参  

sql 注入 及 in 注入的更多相关文章

  1. 注入攻击-SQL注入和代码注入

    注入攻击 OWASP将注入攻击和跨站脚本攻击(XSS)列入网络应用程序十大常见安全风险.实际上,它们会一起出现,因为 XSS 攻击依赖于注入攻击的成功.虽然这是最明显的组合关系,但是注入攻击带来的不仅 ...

  2. sql工具和手工注入总结

    普通注入: 数字注入 字符注入 base64注入:和常规的方法没有说明区别,主要是解码然后编码: 如果普通注入不行,尝试大小写绕过,编码等绕过: 如果不行尝试盲注: POST注入 0x00 常用的 注 ...

  3. StringEscapeUtils的常用使用,防止SQL注入及XSS注入

    StringEscapeUtils的常用使用,防止SQL注入及XSS注入 2017年10月20日 11:29:44 小狮王 阅读数:8974   版权声明:本文为博主原创文章,转载请注明出处. htt ...

  4. 【网络安全】SQL注入、XML注入、JSON注入和CRLF注入科普文

    目录 SQL注入 一些寻找SQL漏洞的方法 防御SQL注入 SQL注入相关的优秀博客 XML注入 什么是XML注入 预防XML注入 JSON注入 什么是JSON注入 JSON注入的防御 CRLF注入 ...

  5. SQL注入:POST注入

    POST注入简介 POST注入属于注入的一种,相信大家在之前的课程中都知道POST\GET两种传参方式. POST注入就是使用POST进行传参的注入,本质上和GET类型的没什么区别. POST注入高危 ...

  6. Go语言SQL注入和防注入

    Go语言SQL注入和防注入 一.SQL注入是什么 SQL注入是一种注入攻击手段,通过执行恶意SQL语句,进而将任意SQL代码插入数据库查询,从而使攻击者完全控制Web应用程序后台的数据库服务器.攻击者 ...

  7. 基础Web漏洞-SQL注入入门(手工注入篇)

    一.什么是SQL注入  SQL是操作数据库数据的结构化查询语言,网页的应用数据和后台数据库中的数据进行交互时会采用SQL.而SQL注入是将Web页面的原URL.表单域或数据包输入的参数,修改拼接成SQ ...

  8. SQL注入之堆叠注入(堆查询注入)

    Stached injection -- 堆叠注入 0x00 堆叠注入的定义 ​ Stacked injection 汉语翻译过来后,称 为堆查询注入,也有称之为堆叠注入.堆叠注入为攻击者提供了很多的 ...

  9. sql server(mssql)联合注入

    sql server(mssql)联合注入 sql server简介: SQL Server 是Microsoft 公司推出的关系型数据库管理系统.具有使用方便可伸缩性好与相关软件集成程度高等优点,可 ...

  10. SQL注入:HEAD注入

    HEAD注入原理 HEAD注入顾名思义就是在传参的时候,将我们的数据构建在http头部. HEAD注入的使用场景 为什么网站要记录你的ip或者请求头,是为了方便你的二次登陆,区分你的登陆地址和设备,可 ...

随机推荐

  1. 理解Vue

    Vue.js是JavaScript MVVM(Model-View-ViewModel)库,十分简洁,Vue核心只关注视图层,相对AngularJS提供更加简洁.易于理解的API.Vue尽可能通过简单 ...

  2. 一. python基础知识

    第一章.变量与判断语句 1.第一个python程序 # -*- coding:utf-8 -*- # Author: Raymond print ("hello world") p ...

  3. 菜鸟学习Cocos2d-x 3.x——内存管理

    菜鸟学习Cocos2d-x 3.x——内存管理 2014-12-10 分类:Cocos2d-x / 游戏开发 阅读(394) 评论(6)    亘古不变的东西 到现在,内存已经非常便宜,但是也不是可以 ...

  4. classList属性和className的区别

    className的不方便之处: 在操作类名时,需要通过className属性添加,删除和替换类名.因为className中是一个字符串,所以即使只修改字符串一部分,也必须每次都设置整个字符串的值.( ...

  5. (1) zabbix进程构成

    进程介绍 zabbix_agentd客户端守护进程,此进程收集客户端数据,例如cpu负载.内存.硬盘使用情况等 zabbix_getzabbix工具,单独使用的命令,通常在server或者proxy端 ...

  6. ES5中新增的forEach等新方法的一些使用声明

    转载地址:http://www.zhangxinxu.com/wordpress/?p=3220 一.前言-索引 ES5中新增的不少东西,了解之对我们写JavaScript会有不少帮助,比如数组这块, ...

  7. verilog behaviral modeling -- procedural timing contronls

    1.delay control : an expression specifies the time duration between initially encountering the state ...

  8. IE8 png图片黑色阴影的解决方案!

    以前都没有留意这个问题,最近开发中才发现. 什么情况下会出现黑边? PNG透明图片,同时有阴影,具备着两个IE8是不会有问题的,再加上使用了 “渐变显示”,就会出现了. 解决方法: img{displ ...

  9. python库——h5py入门讲解

    本文只是简单的对h5py库的基本创建文件,数据集和读取数据的方式进行介绍,作者刚接触h5py,完全靠看文档自学,如果哪里说的不对,欢迎纠正!如果读者需要进一步详细的学习h5py的更多知识,请参考h5p ...

  10. luogu4301 [CQOI2013]新Nim游戏

    nim和线性基 #include <algorithm> #include <iostream> #include <cstdio> using namespace ...