数据库

位置:IT落伍者 >> 数据库 >> 浏览文章

SQL Server参数化查询大数据下的实践


发布日期:2021年09月10日
 
SQL Server参数化查询大数据下的实践

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

where in 的参数化查询实现

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

string userIds = "";

using (SqlConnection conn = new SqlConnection(connectionString))

{

connOpen();

SqlCommand comm = new SqlCommand();

commConnection = conn;

commCommandText = stringFormat("select * from Users(nolock) where UserID in({})" userIds);

commExecuteNonQuery();

}

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

using (SqlConnection conn = new SqlConnection(connectionString))

{

connOpen();

SqlCommand comm = new SqlCommand();

commConnection = conn;

commCommandText = "select * from Users(nolock) where UserID in(@UserID)";

commParametersAdd(new SqlParameter("@UserID" SqlDbTypeVarChar ) { Value = "" });

commExecuteNonQuery();

}

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

select * from Users(nolock) where UserID in()

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

using (SqlConnection conn = new SqlConnection(connectionString))

{

connOpen();

SqlCommand comm = new SqlCommand();

commConnection = conn;

commCommandText = "select * from Users(nolock) where UserName in(@UserName)";

commParametersAdd(new SqlParameter("@UserName" SqlDbTypeVarChar ) { Value = "johndudurabbit" });

commExecuteNonQuery();

}

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

select * from Users(nolock) where UserName in(johndudurabbit)

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

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

using (SqlConnection conn = new SqlConnection(connectionString))

{

connOpen();

SqlCommand comm = new SqlCommand();

commConnection = conn;

//使用CHARINDEX实现参数化查询可以复用查询计划同时会使索引失效

commCommandText = "select * from Users(nolock) where CHARINDEX(+ltrim(str(UserID))++@UserID+)>";

commParametersAdd(new SqlParameter("@UserID" SqlDbTypeVarChar ) { Value = "" });

commExecuteNonQuery();

}

using (SqlConnection conn = new SqlConnection(connectionString))

{

connOpen();

SqlCommand comm = new SqlCommand();

commConnection = conn;

//使用like实现参数化查询可以复用查询计划同时会使索引失效

commCommandText = "select * from Users(nolock) where +@UserID+ like %+ltrim(str(UserID))+% ";

commParametersAdd(new SqlParameter("@UserID" SqlDbTypeVarChar ) { Value = "" });

commExecuteNonQuery();

}

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

using (SqlConnection conn = new SqlConnection(connectionString))

{

connOpen();

SqlCommand comm = new SqlCommand();

commConnection = conn;

//使用exec动态执行SQL//实际执行的查询计划为(@UserID varchar(max))select * from Users(nolock) where UserID in ()//不是预期的(@UserID varchar(max))exec(select * from Users(nolock) where UserID in (+@UserID+)) commCommandText = "exec(select * from Users(nolock) where UserID in (+@UserID+))";

commParametersAdd(new SqlParameter("@UserID" SqlDbTypeVarChar ) { Value = "" });

commExecuteNonQuery();

}

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

using (SqlConnection conn = new SqlConnection(connectionString))

{

connOpen();

SqlCommand comm = new SqlCommand();

commConnection = conn;

//为每一条数据添加一个参数

commCommandText = "select * from Users(nolock) where UserID in (@UserID@UserId@UserID@UserID)";

commParametersAddRange(

new SqlParameter[]

{

new SqlParameter("@UserID" SqlDbTypeInt) { Value = }

new SqlParameter("@UserID" SqlDbTypeInt) { Value = }

new SqlParameter("@UserID" SqlDbTypeInt) { Value = }

new SqlParameter("@UserID" SqlDbTypeInt) { Value = }

});

commExecuteNonQuery();

}

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

using (SqlConnection conn = new SqlConnection(connectionString))

{

connOpen();

SqlCommand comm = new SqlCommand();

commConnection = conn;

string sql = @"

declare @Temp_Variable varchar(max)

create table #Temp_Table(Item varchar(max))

while(LEN(@Temp_Array) > )

begin

if(CHARINDEX(@Temp_Array) = )

begin

set @Temp_Variable = @Temp_Array

set @Temp_Array =

end

else

begin

set @Temp_Variable = LEFT(@Temp_ArrayCHARINDEX(@Temp_Array))

set @Temp_Array = RIGHT(@Temp_ArrayLEN(@Temp_Array)LEN(@Temp_Variable))

end

insert into #Temp_Table(Item) values(@Temp_Variable)

end

select * from Users(nolock) where exists(select from #Temp_Table(nolock) where #Temp_TableItem=UsersUserID)

drop table #Temp_Table";

commCommandText = sql;

commParametersAdd(new SqlParameter("@Temp_Array" SqlDbTypeVarChar ) { Value = "" });

commExecuteNonQuery();

}

like参数化查询

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

using (SqlConnection conn = new SqlConnection(connectionString))

{

connOpen();

SqlCommand comm = new SqlCommand();

commConnection = conn;

//将 % 写到参数值中

commCommandText = "select * from Users(nolock) where UserName like @UserName";

commParametersAdd(new SqlParameter("@UserName" SqlDbTypeVarChar ) { Value = "rabbit%" });

commExecuteNonQuery();

}

using (SqlConnection conn = new SqlConnection(connectionString))

{

connOpen();

SqlCommand comm = new SqlCommand();

commConnection = conn;

//SQL中拼接 %

commCommandText = "select * from Users(nolock) where UserName like @UserName+%";

commParametersAdd(new SqlParameter("@UserName" SqlDbTypeVarChar ) { Value = "rabbit%" });

commExecuteNonQuery();

}

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

               

上一篇:如何减少SQL Server死锁发生

下一篇:SQL Server执行动态SQL正确方式