一般情况下,SQL查询是相对固定的,一条语句变化的可能只是条件值,比如之前要求查询二年级学生信息,而后面需要查询三年级的信息,这样的查询一般查询的列不变,后面的条件只有值在变化,针对这种查询可以使用参数化查询的方式来提高效率,也可以时SQL操作更加安全,从根本上杜绝SQL注入的问题。
参数化查询的优势:
- 提高效率:之前说过,数据库在执行SQL的过程中,每次都会经过SQL的解析,编译,调用对应的数据库组件,这样如果执行多次同样类型的SQL语句,解析,编译的过程明显是在浪费资源,而参数化查询就是使用编译好的过程(也就是提前告诉数据库要调用哪些数据库组件),这样就跳过了对SQL语句的解析,编译过程,提高了效率(这个过程我觉得有点类似于C/C++语言的编译执行与脚本语言的解释执行)。
- 更加安全:从安全编程的角度来说,对于防范SQL注入方面,它比关键字过滤更有效,实现起来也更加方便。
科普SQL注入和安全编程
什么是SQL注入:
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。举个例子来说在用户登录时会输入用户名密码,这个时候在后台就可以执行这样的SQL语句
1
select count(*) from user where username = 'haha' and password = '123456'
只有输入对了用户名和密码才能登录,但是如果没有对用户输入进行校验,当用户输入一些SQL中的语句,而后台直接将用户输入进行拼接并执行,就会发生注入,比如此时用户输入 ‘haha’ or 1 = 1 – ,此时再后台执行的sql语句就变成了这样:
1
select count(*) from user where username = 'haha' or 1 = 1 -- and password = ''
这样用户就可以不用密码,直接使用用户名就登录了。而防范这类攻击,一般采用的是关键字过滤的方式,但是关键字过滤并不能杜绝这类工具,当一时疏忽忘记了过滤某个关键字仍然会产生这类问题。而且关键字过滤一般采用正则表达式,而正则表达式并不是一般人可以驾驭的。而防范SQL注入最简单也是最一劳永逸的方式就是参数化查询。
为什么参数化查询能够从根本上解决SQL注入
发生SQL注入一般的原因是程序将用户输入当做SQL语句的一部分进行执行,但是参数化查询它只是将用户输入当做参数,当做查询的条件,从数据库的层面上来说,它不对应于具体的数据库组件,它只是一组数据,而不会执行。这里可以简单的将传统的SQL拼接方式理解为C语言中的宏,宏也可以有参数,但是它不对参数进行校验,只是简单的进行替换,那么我可以使用一些指令作为参数传入,但是函数就不一样,函数的参数就是具体类型的变量或者常量。所以参数化查询从根本上解决的SQL注入的问题。
参数化查询的使用
前面说了这么多参数化查询的好处,那么到底怎么使用它呢?
在Java等语言中内置了数据库操作,而对于C/C++来说,它并没有提供这方方面的标准。不同的平台有自己独特的一套机制,但是从总体来说,思想是共通的,只是语法上的不同,这里主要是说明OLEDB中的使用方式。
使用“?”符将SQL语句中的条件值常量进行替换,组成一个新的SQL语句,比如上面登录的查询语句可以写成
1
select count(*) from user where username = ? and password = ?
调用ICommandText的SetCommandText设置sql语句。
- 调用ICommandParpare的Prepare方法对含有”?”的语句进行预处理
- 调用ICommandWithParameters方法的GetParameterInfo方法获取参数详细信息的DBPARAMINFO结构(类似于DBCOLUMNINFO)
- 分配对应大小的DBBINDING缓冲用来保存每个参数的绑定信息
- 调用IAccessor的CreateAccessor方法创建对应的访问器
- 为参数分配缓冲,设置合适的参数后准备DBPARAMS结构
- 调用ICommandText的Execute方法并将DBPARAMS结构的指针作为参数传入。
- 操作返回的结果集对象
1 | typedef struct tagDBPROPIDSET { |
DBPARAMS结构的定义如下:1
2
3
4
5
6typedef struct tagDBPARAMS
{
void *pData;
DB_UPARAMS cParamSets;
HACCESSOR hAccessor;
} DBPARAMS;
- pData是保存参数信息的缓冲;
- cParamSets: 表示又多少个参数
- hAccessor: 之前获取到的绑定结构的访问器句柄
下面是一个使用的例子:
1 | BOOL QueryData(LPOLESTR pQueryStr, IOpenRowset* pIOpenRowset, IRowset* &pIRowset) |