jdbc测试mysql数据库sql预解析(绑定变量) 用习惯了oracle,学习mysql,想测试一下mysql绑定变量的效果。以前看网上介绍大部份都说mysql没有sql共享池的概念,所以也不存在sql预解析或绑定变量的说法。 今天测试了一下(通过网络抓包、查看服务器端sql日志及
jdbc测试mysql数据库sql预解析(绑定变量)
用习惯了oracle,学习mysql,想测试一下mysql绑定变量的效果。以前看网上介绍大部份都说mysql没有sql共享池的概念,所以也不存在sql预解析或绑定变量的说法。
今天测试了一下(通过网络抓包、查看服务器端sql日志及分析源码等方法),发现mysql还是有sql预解析的实现。
服务器端是mysql 5.1.58(win32),用jdbc(5.1.18)做客户端,默认的连接方式是不会有sql预解析效果,即使我们用preparedstatement对象也差不多,它只是把sql和变量拼接成一个完整的sql发送给服务器,如下代码:
preparedstatement pstmt = conn.preparestatement(select * from t1 where c1=?);pstmt.setstring(1, abc);pstmt.execute();
实际上不会有预解析的过程,而是经过简单的拼接,把如下sql发送给服务器
select * from t1 where c1='abc'
要实现预解析的效果,我们必须设置jdbc connection的参数useserverprepstmts=true,再使用preparedstatement后就ok了,创建preparedstatement时客户端先把select * from t1 where c1=?发送到服务器端预解析,execute时只是把变量传送到服务器执行。
mysql服务器的sql语句缓存可以通过状态变量prepared_stmt_count查看
mysql> show status like 'prepared_stmt_count';+---------------------+-------+| variable_name | value |+---------------------+-------+| prepared_stmt_count | 1 |+---------------------+-------+1 row in set
不过mysql的sql语句缓存与oracle有很大不同,它是会话语句级的,不是全局共享,当会话断开或preparedstatement.close后这个缓存就没有了。我们需要设置connection的参数cacheprepstmts=true把preparedstatement缓存起来,prepstmtcachesize=xxx来设置每个会话缓存语句的最大数量(很多连接池也有类似的功能)。
ok,已经知道如何启用预解析了,想看看启用与不启用预解析性能有多少差别,会不会也像oracle那么明显呢?经过简单的测试,发现当没有preparedstatement缓存(cacheprepstmts=false)时,打开预解析性能下降很多, 当有preparedstatement缓存(cacheprepstmts=true)时,两者性能基本一样。这个结果让人很失望,个人分析有几个原因:
启用预解析但没有preparedstatement缓存时,每次创建preparedstatement都需要解析一次,execute时又需要交互一次,而预解析的sql在preparedstatement.close又不能重用,所以性能反而更差。
当有preparedstatement缓存时,预解析的sql文本缓存在服务器端,但是并不会像oracle一样缓存执行计划,所以每次execute时都需要解析sql和生成执行计划,因此只是减少了每次execute传输sql的文本大小,性能差别不大。
注:如果sql语法错误,那么服务器端预解析会出错,但jdbc收到预解析出错的信息后并不提示出错,而是将取消本条语句预解析的状态,execute时直接把sql接装发送给服务器,mysql jdbc在preparedstatement构造函数中代码如下,其中返回serverpreparedstatement类表示使用了绑定变量,返回preparedstatement表示未使用绑定变量:
try { pstmt = serverpreparedstatement.getinstance(getloadbalancesafeproxy(), nativesql, this.database, resultsettype, resultsetconcurrency); pstmt.setresultsettype(resultsettype); pstmt.setresultsetconcurrency(resultsetconcurrency);} catch (sqlexception sqlex) { // punt, if necessary if (getemulateunsupportedpstmts()) { pstmt = (preparedstatement) clientpreparestatement(nativesql, resultsettype, resultsetconcurrency, false); } else { throw sqlex; }}
经过上面分析,个人认为不需要打开sql预解析的效果,preparedstatement对象还是尽量使用,因为虽然不能提升性能,但可以避免sql注入安全问题 。
2012-02-17