梦在远方的小猪

感谢原作者...  后面总结的五点感觉挺好的.. 自己之前的知识点一直没有串起来.

转帖记录一下感谢.

sql参数化参数化

说来惭愧,工作差不多4年了,直到前些日子被DBA找上门让我优化一个CPU占用很高的复杂SQL语句时,我才突然意识到了参数化查询的重要性。

相信有很多开发者和我一样对于参数化查询认识比较模糊,没有引起足够的重视

错误认识1.不需要防止sql注入的地方无需参数化
  参数化查询就是为了防止SQL注入用的,其它还有什么用途不知道、也不关心,原则上是能不用参数就不用参数,为啥?多麻烦,我只是做公司内部系统不用担心SQL注入风险,使用参数化查询不是给自己找麻烦,简简单单拼SQL,万事OK

错误认识2.参数化查询时是否指定参数类型、参数长度没什么区别
  以前也一直都觉的加与不加参数长度 应该没有什么区别,仅是写法上的不同而已,而且觉得加参数类型和长度写法太麻烦,最近才明白其实两者不一样的,为了提高sql执行速度,请为 SqlParameter参数加上SqlDbType和size属性,在参数化查询代码编写过程中很多开发者忽略了指定查询参数的类型,这将导致托管代码 在执行过程中不能自动识别参数类型,进而对该字段内容进行全表扫描以确定参数类型并进行转换,消耗了不必要的查询性能所致。根据MSDN解释:如果未在 size参数中显式设置Size,则从dbType参数的值推断出该大小。如果你认为上面的推断出该大小是指从SqlDbType类型推断,那你就错了, 它实际上是从你传过来的参数的值来推断的,比如传递过来的值是"username",则size值为8,"username1",则size值为9。那 么,不同的size值会引发什么样的结果呢?且经测试发现,size的值不同时,会导致数据库的执行计划不会重用,这样就会每次执行sql的时候重新生成 新的执行计划,而浪费数据库执行时间。

下面来看具体测试

首先清空查询计划

DBCC FREEPROCCACHE

传值username,不指定参数长度,生成查询计划

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserName=@UserName";
    //传值 username,不指定参数长度
    //查询计划为(@UserName varchar(8))select * from Users where UserName=@UserName
    comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar) { Value = "username" });
    comm.ExecuteNonQuery();
}

传值username1,不指定参数长度,生成查询计划

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserName=@UserName";
    //传值 username1,不指定参数长度
    //查询计划为(@UserName varchar(9))select * from Users where UserName=@UserName
    comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar) { Value = "username1" });
    comm.ExecuteNonQuery();
}

传值username,指定参数长度为50,生成查询计划

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserName=@UserName";
    //传值 username,指定参数长度为50
    //查询计划为(@UserName varchar(50))select * from Users where UserName=@UserName
    comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar,50) { Value = "username" });
    comm.ExecuteNonQuery();
}

传值username1,指定参数长度为50,生成查询计划

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserName=@UserName";
    //传值 username1,指定参数长度为50
    //查询计划为(@UserName varchar(50))select * from Users where UserName=@UserName
    comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar,50) { Value = "username1" });
    comm.ExecuteNonQuery();
}

使用下面语句查看执行的查询计划

SELECT cacheobjtype,objtype,usecounts,sql FROM sys.syscacheobjects
WHERE sql LIKE '%Users%'  and sql not like '%syscacheobjects%'

结果如下图所示

可以看到指定了参数长度的查询可以复用查询计划,而不指定参数长度的查询会根据具体传值而改变查询计划,从而造成性能的损失。

这里的指定参数长度仅指可变长数据类型,主要指varchar,nvarchar,char,nchar等,对于 int,bigint,decimal,datetime等定长的值类型来说,无需指定(即便指定了也没有用),详见下面测试,UserID为int类 型,无论长度指定为2、20、-1查询计划都完全一样为(@UserIDint)select*from Users where UserID=@UserID

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserID=@UserID";
    //传值 2,参数长度2
    //执行计划(@UserID int)select * from Users where UserID=@UserID
    comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.Int, 2) { Value = 2 });
    comm.ExecuteNonQuery();
}
using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserID=@UserID";
    //传值 2,参数长度20
    //执行计划(@UserID int)select * from Users where UserID=@UserID
    comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.Int, 20) { Value = 2 });
    comm.ExecuteNonQuery();
}
using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserID=@UserID";
    //传值 2,参数长度-1
    //执行计划(@UserID int)select * from Users where UserID=@UserID
    comm.Parameters.Add(new SqlParameter("@UserID", SqlDbType.Int, -1) { Value = 2 });
    comm.ExecuteNonQuery();
}

这里提一下,若要传值varchar(max)或nvarchar(max)类型怎么传,其实只要设定长度为-1即可

using (SqlConnection conn = new SqlConnection(connectionString))
{
    conn.Open();
    SqlCommand comm = new SqlCommand();
    comm.Connection = conn;
    comm.CommandText = "select * from Users where UserName=@UserName";
    //类型为varchar(max)时,指定参数长度为-1
    //查询计划为 (@UserName varchar(max) )select * from Users where UserName=@UserName
    comm.Parameters.Add(new SqlParameter("@UserName", SqlDbType.VarChar,-1) { Value = "username1" });
    comm.ExecuteNonQuery();
}

当然了若是不使用参数化查询,直接拼接SQL,那样就更没有查询计划复用一说了,除非你每次拼的SQL都完全一样

总结,参数化查询意义及注意点

1.可以防止SQL注入

2.可以提高查询性能(主要是可以复用查询计划),这点在数据量较大时尤为重要

3.参数化查询参数类型为可变长度时(varchar,nvarchar,char等)请指定参数类型及长度,若为值类型(int,bigint,decimal,datetime等)则仅指定参数类型即可

4.传值为varchar(max)或者nvarchar(max)时,参数长度指定为-1即可

5.看到有些童鞋对于存储过程是否要指定参数长度有些疑惑,这里补充下,若调用的是存储过程时,参数无需指定长度,如果指定了也会忽略,以存储过程 中定义的长度为准,不会因为没有指定参数长度而导致重新编译,不过还是建议大家即便时调用存储过程时也加上长度,保持良好的变成习惯

[转帖] SQL参数化的优点 CopyFrom https://www.cnblogs.com/-lzb/articles/4840671.html的更多相关文章

  1. 转发自:一像素 十大经典排序算法(动图演示)原链接:https://www.cnblogs.com/onepixel/articles/7674659.html 个人收藏所用 侵删

    原链接:https://www.cnblogs.com/onepixel/articles/7674659.html     个人收藏所用   侵删 0.算法概述 0.1 算法分类 十种常见排序算法可 ...

  2. 网上看到一份详细sql游标说明 《转载 https://www.cnblogs.com/xiongzaiqiren/p/sql-cursor.html》

     SQL游标(cursor)详细说明及内部循环使用示例 游标 游标(cursor)是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果.每个游标区都有一个名字,用户可以用SQL语句逐一从游标中获 ...

  3. https://www.cnblogs.com/yuanchenqi/articles/6755717.html

    知识预览 一 进程与线程的概念 二 threading模块 三 multiprocessing模块 四 协程 五 IO模型 回到顶部 一 进程与线程的概念 1.1 进程 考虑一个场景:浏览器,网易云音 ...

  4. 比较好的Dapper封装的仓储实现类 来源:https://www.cnblogs.com/liuchang/articles/4220671.html

    using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; usin ...

  5. Hive和HBase的区别 转载:https://www.cnblogs.com/guoruibing/articles/9894521.html

    1.Hive和HBase的区别 1)hive是sql语言,通过数据库的方式来操作hdfs文件系统,为了简化编程,底层计算方式为mapreduce. 2)hive是面向行存储的数据库. 3)Hive本身 ...

  6. 转 jvisualvm 工具使用 https://www.cnblogs.com/kongzhongqijing/articles/3625340.html

    VisualVM 是Netbeans的profile子项目,已在JDK6.0 update 7 中自带(java启动时不需要特定参数,监控工具在bin/jvisualvm.exe). https:// ...

  7. urllib 模块 https://www.cnblogs.com/guishou/articles/7089496.html

    1.基本方法 urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=Fals ...

  8. Git命令<转载 https://www.cnblogs.com/cspku/articles/Git_cmds.html>

    查看.添加.提交.删除.找回,重置修改文件 git help <command> # 显示command的help git show # 显示某次提交的内容 git show $id gi ...

  9. python面试题(转自https://www.cnblogs.com/wupeiqi/p/9078770.html)

    第一部分 Python基础篇(80题) 为什么学习Python? 通过什么途径学习的Python? Python和Java.PHP.C.C#.C++等其他语言的对比? 简述解释型和编译型编程语言? P ...

随机推荐

  1. kubelet 初始化过程

    kubelet 初始化过程 . kubelet 启动 . kubelet认为,它并没有有一个kubeconfig文件 . kubelet搜索并查找bootstrap-kubeconfig文件 . ku ...

  2. ethereum/EIPs-1193 Ethereum Provider JavaScript API 如metamask更新后的接口

    eip title author discussions-to status type category created requires 1193 Ethereum Provider JavaScr ...

  3. Java连接Redis之redis的增删改查

    一.新建一个maven工程,工程可以以jar的形式或war都行,然后导入正确的依赖 <project xmlns="http://maven.apache.org/POM/4.0.0& ...

  4. 谈谈maven多模块

    记得在校的时候,通常用的比较多是动态web工程,动态web工程导入最多就是jar包,这些jar包需要自己手动复制粘贴放入lib目录下,然后build path,有的IDE会自动build path有的 ...

  5. MSComm控件与Win32 API操作串口有何区别?

    MSComm控件与Win32 API操作串口有何区别? [问题点数:50分,结帖人shell_shell]   收藏帖子 回复 我是一个小兵,在战场上拼命!   结帖率 83.33% 我以前用MSCo ...

  6. Linux系列教程(七)——Linux常用命令之帮助和用户管理命令

    上篇博客我们介绍了Linux文件搜索命令,其中find是用的最多的也是功能最强大的文件或目录搜索命令,和另一个搜索命令locate的区别是,find命令是全盘搜索,刚创建的文件也能搜索的到,而loca ...

  7. BZOJ3451 Normal 期望、点分治、NTT

    BZOJCH传送门 题目大意:给出一棵树,求对其进行随机点分治的复杂度期望 可以知道一个点的贡献就是其点分树上的深度,也就是这个点在点分树上的祖先数量+1. 根据期望的线性性,考虑一个点对\((x,y ...

  8. (转)Ubuntu init启动流程分析

    原文 upstart homepage 现行的Linux distros主流的有两种init方式:一种是广为流传的System V initialization,它来源于Unix并且至今仍被各种Lin ...

  9. LiveCharts文档-3开始-1安装

    原文:LiveCharts文档-3开始-1安装 LiveCharts文档-3开始-1安装 我不会逐字逐句翻译,有些过于基本的地方语言上会所略 三个平台我只翻译WinForm,其他的WPF和UWP大部分 ...

  10. EZ 2018 05 20 NOIP2018 模拟赛(十五)

    这次的比赛充满着玄学的气息,玄学链接 首先讲一下为什么没有第十四场 其实今天早上9点时看到题目就叫了:原题! 没错,整套试卷都做过,我还写了题解 然后老叶就说换一套,但如果仅仅是这样就没什么 但等13 ...