前几天因为工作的原因,看了下memcached的相关资料,资料中谈到了一个缓存策略:
“将要执行的sql查询语句用md5编码后,作为key,将执行的结果作为value放到缓存中”,这句话
一下子让我想起了自己一直不明白的一个技术,就是JDBC中的预编译语句(preparedStatement)。
记得学习JDBC的时候,老师解释过说用preparedStatement比用statement要好,推荐就用预编译
的,至于其中的道理,老师并未更深的阐述,后来随着经验的增多,以及对Oracle数据库的了解,
知道了数据库是有缓存策略的,即当客户端向数据库服务器发送一个sql语句后,服务器有一个
解析过程,这个过程大致是这些步骤:
1、语法检查,检查sql的语法是否正确
2、权限检查,检查当前的用户是否有权限执行这个sql语句
3、编译,将我们的sql编译成二进制的机器码(这是我自己的理解,可能不是很正确,大致如此)
4、选择查询策略
5、真正执行查询
这个过程叫硬解析,即每一个sql都要重复这些过程。
而现在的数据库是有缓存策略的,即它会把客户端发过来的待执行的sql语句以key,value的形式
缓存起来,value中放的应该是上述第四步的执行结果,如果发现当前的sql命中,则直接从缓存
中找到被编译过的语句直接就执行了,这个过程也叫软解析,用伪代码表示如下
if(sql命中){
直接执行该sql
}else{
进行硬解析,执行上述的5步
}
从这个伪代码中可以看出,软解析的效率要高于硬解析,其实想一想,缓存的思想也就是最基本
的编程原则DRY(Don't Repeat Yourself)的体现。
好,了解了数据库服务器中的缓存策略后,再回到JDBC中的preparedStatement和statement来,
如果我们写了一条statement语句,比如,select * from emp where id=1 and name='fhj',
当把这条语句发给数据库时,数据库执行完后会把它给缓存起来,只有我们再次重复和它一模一样
的语句时,缓存才会生效,如果我们执行的是select * from emp where id=2 and name='yxd',
则不会使用缓存,因为对数据库而言,这两条并非同一个语句,尽管在我们看来这两条语句仅是
数据的不同,算法是相同的,但在机器看来却不是这样。好了,这时preparedStatement就起了
作用,我们这样写:select * from emp where id=:id and name=:name,然后再对id和name赋上
我们期待的数据,这时,数据库就会对这条语句进行缓存,以后只要我们把类似的语句发给数据库,
则都会命中缓存中的已经解析过的语句,执行速度当然就会快了。
同时,通过上述的分析过程,也能解释为什么预编译的语句就能避免sql注入,而普通的statement
则不能:因为对于普通的statement,服务器是把它当成一个源代码进行编译,其实就是对一个
字符串进行编译;而对于preparedStatement来说,命令的结构已经编译完成了,算法已经确定,
数据库要做的仅是一个赋值的动作而已,所以就避免了sql注入。
通过上述的论述,我认为我的整体思路应该是对的,不过还存在两点疑问需要证实:
1、是否可以查询数据库服务器端已经缓存的语句,来证明这两种语句都会被缓存。
记得以前就这个问题问过公司的DBA,但是当时的经过和结果已经记不清了,如果有朋友
看到了我的这篇博客,还请不吝赐教如何查询。
2、我相信通过statement和preparedStatement这两种形式发送给服务器端的数据是不一样的,
第一种就是一个完整的数据,第二种是不是有可能分成两次和数据库进行的交互,即先发
送要查询的语句,随后再发送要查询的数据,这个不知道有没有什么办法可以看到。
“将要执行的sql查询语句用md5编码后,作为key,将执行的结果作为value放到缓存中”,这句话
一下子让我想起了自己一直不明白的一个技术,就是JDBC中的预编译语句(preparedStatement)。
记得学习JDBC的时候,老师解释过说用preparedStatement比用statement要好,推荐就用预编译
的,至于其中的道理,老师并未更深的阐述,后来随着经验的增多,以及对Oracle数据库的了解,
知道了数据库是有缓存策略的,即当客户端向数据库服务器发送一个sql语句后,服务器有一个
解析过程,这个过程大致是这些步骤:
1、语法检查,检查sql的语法是否正确
2、权限检查,检查当前的用户是否有权限执行这个sql语句
3、编译,将我们的sql编译成二进制的机器码(这是我自己的理解,可能不是很正确,大致如此)
4、选择查询策略
5、真正执行查询
这个过程叫硬解析,即每一个sql都要重复这些过程。
而现在的数据库是有缓存策略的,即它会把客户端发过来的待执行的sql语句以key,value的形式
缓存起来,value中放的应该是上述第四步的执行结果,如果发现当前的sql命中,则直接从缓存
中找到被编译过的语句直接就执行了,这个过程也叫软解析,用伪代码表示如下
if(sql命中){
直接执行该sql
}else{
进行硬解析,执行上述的5步
}
从这个伪代码中可以看出,软解析的效率要高于硬解析,其实想一想,缓存的思想也就是最基本
的编程原则DRY(Don't Repeat Yourself)的体现。
好,了解了数据库服务器中的缓存策略后,再回到JDBC中的preparedStatement和statement来,
如果我们写了一条statement语句,比如,select * from emp where id=1 and name='fhj',
当把这条语句发给数据库时,数据库执行完后会把它给缓存起来,只有我们再次重复和它一模一样
的语句时,缓存才会生效,如果我们执行的是select * from emp where id=2 and name='yxd',
则不会使用缓存,因为对数据库而言,这两条并非同一个语句,尽管在我们看来这两条语句仅是
数据的不同,算法是相同的,但在机器看来却不是这样。好了,这时preparedStatement就起了
作用,我们这样写:select * from emp where id=:id and name=:name,然后再对id和name赋上
我们期待的数据,这时,数据库就会对这条语句进行缓存,以后只要我们把类似的语句发给数据库,
则都会命中缓存中的已经解析过的语句,执行速度当然就会快了。
同时,通过上述的分析过程,也能解释为什么预编译的语句就能避免sql注入,而普通的statement
则不能:因为对于普通的statement,服务器是把它当成一个源代码进行编译,其实就是对一个
字符串进行编译;而对于preparedStatement来说,命令的结构已经编译完成了,算法已经确定,
数据库要做的仅是一个赋值的动作而已,所以就避免了sql注入。
通过上述的论述,我认为我的整体思路应该是对的,不过还存在两点疑问需要证实:
1、是否可以查询数据库服务器端已经缓存的语句,来证明这两种语句都会被缓存。
记得以前就这个问题问过公司的DBA,但是当时的经过和结果已经记不清了,如果有朋友
看到了我的这篇博客,还请不吝赐教如何查询。
2、我相信通过statement和preparedStatement这两种形式发送给服务器端的数据是不一样的,
第一种就是一个完整的数据,第二种是不是有可能分成两次和数据库进行的交互,即先发
送要查询的语句,随后再发送要查询的数据,这个不知道有没有什么办法可以看到。