热门标签 | HotTags
当前位置:  开发笔记 > 数据库 > 正文

SQLServer参数化查询大数据下的实践

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

身为一名小小的程序员,在日常开发中不可以避免的要和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();

  }

  我的写作热情离不开您的肯定支持。


推荐阅读
  • 本文介绍了使用postman进行接口测试的方法,以测试用户管理模块为例。首先需要下载并安装postman,然后创建基本的请求并填写用户名密码进行登录测试。接下来可以进行用户查询和新增的测试。在新增时,可以进行异常测试,包括用户名超长和输入特殊字符的情况。通过测试发现后台没有对参数长度和特殊字符进行检查和过滤。 ... [详细]
  • 延迟注入工具(python)的SQL脚本
    本文介绍了一个延迟注入工具(python)的SQL脚本,包括使用urllib2、time、socket、threading、requests等模块实现延迟注入的方法。该工具可以通过构造特定的URL来进行注入测试,并通过延迟时间来判断注入是否成功。 ... [详细]
  • MySQL中的MVVC多版本并发控制机制的应用及实现
    本文介绍了MySQL中MVCC的应用及实现机制。MVCC是一种提高并发性能的技术,通过对事务内读取的内存进行处理,避免写操作堵塞读操作的并发问题。与其他数据库系统的MVCC实现机制不尽相同,MySQL的MVCC是在undolog中实现的。通过undolog可以找回数据的历史版本,提供给用户读取或在回滚时覆盖数据页上的数据。MySQL的大多数事务型存储引擎都实现了MVCC,但各自的实现机制有所不同。 ... [详细]
  • 用友深耕烟草行业25年,提出数字化转型建议
    本文介绍了用友在烟草行业深耕25年的经验,提出了数字化转型的建议,包括总体要求、主要任务、发展阶段和六位一体推进举措。通过数字化转型,烟草行业将注入新动能,实现高质量发展。 ... [详细]
  • Principle for Mac(交互式屏幕设计软件)免激活版
    Mac上好用的交互式屏幕设计软件,PrincipleforMac是一款交互式屏幕设计软件,principle mac让您的设计将以原则出现,随时为您注入新的活力。如果您进行更改,再 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • 前景:当UI一个查询条件为多项选择,或录入多个条件的时候,比如查询所有名称里面包含以下动态条件,需要模糊查询里面每一项时比如是这样一个数组条件:newstring[]{兴业银行, ... [详细]
  • Oracle10g备份导入的方法及注意事项
    本文介绍了使用Oracle10g进行备份导入的方法及相关注意事项,同时还介绍了2019年独角兽企业重金招聘Python工程师的标准。内容包括导出exp命令、删用户、创建数据库、授权等操作,以及导入imp命令的使用。详细介绍了导入时的参数设置,如full、ignore、buffer、commit、feedback等。转载来源于https://my.oschina.net/u/1767754/blog/377593。 ... [详细]
  • mysql-cluster集群sql节点高可用keepalived的故障处理过程
    本文描述了mysql-cluster集群sql节点高可用keepalived的故障处理过程,包括故障发生时间、故障描述、故障分析等内容。根据keepalived的日志分析,发现bogus VRRP packet received on eth0 !!!等错误信息,进而导致vip地址失效,使得mysql-cluster的api无法访问。针对这个问题,本文提供了相应的解决方案。 ... [详细]
  • ubuntu用sqoop将数据从hive导入mysql时,命令: ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 如何在php中将mysql查询结果赋值给变量
    本文介绍了在php中将mysql查询结果赋值给变量的方法,包括从mysql表中查询count(学号)并赋值给一个变量,以及如何将sql中查询单条结果赋值给php页面的一个变量。同时还讨论了php调用mysql查询结果到变量的方法,并提供了示例代码。 ... [详细]
  • Oracle seg,V$TEMPSEG_USAGE与Oracle排序的关系及使用方法
    本文介绍了Oracle seg,V$TEMPSEG_USAGE与Oracle排序之间的关系,V$TEMPSEG_USAGE是V_$SORT_USAGE的同义词,通过查询dba_objects和dba_synonyms视图可以了解到它们的详细信息。同时,还探讨了V$TEMPSEG_USAGE的使用方法。 ... [详细]
  • 在使用dedecms过程中,添加自定义字段变量很有用,但删除并不容易。本文介绍了两种常用的删除方法:执行SQL语句和手动SQL删除。 ... [详细]
  • MyBatis多表查询与动态SQL使用
    本文介绍了MyBatis多表查询与动态SQL的使用方法,包括一对一查询和一对多查询。同时还介绍了动态SQL的使用,包括if标签、trim标签、where标签、set标签和foreach标签的用法。文章还提供了相关的配置信息和示例代码。 ... [详细]
author-avatar
HANK_LIU刘浩象_862
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有