首页 > 代码库 > 如何使用PreparedStatement
如何使用PreparedStatement
Statement执行过程
一个sql语句执行过程中,将经历这么几个步骤:
传输sql到数据库。
数据库检查sql的语法合法性,并解析sql。
计算Access Plan。数据库会通过检测index,statistics来给出最优的访问计划。
根据访问计划进行检索,返回数据。
在上面步骤中,第3步是非常耗时的。因此,为了提高性能,数据库会缓存执行语句以及其Access Plan。这被称为statement cache。在statement cache中,sql语句本身为key,access plan为value。当相同的sql语句被发送过来时,数据库会使用缓存中的access plan以节省cpu时间。
看下面一段code:
Statement statement = connection.createStatement(); String sql1="Select * from test where id=1"; String sql2="Select * from test where id="; statement.execute(sql1); statement.execute(sql1); statement.execute(sql1); statement.execute(sql2+"2"); statement.execute(sql2+"3");
sql1在第一次执行的时候,需要计算执行计划。但在第2和3次执行的时候,会使用缓存好的执行计划,因此后面的sql1不会再重新检验语法与计算执行计划,效率会比第一次高。
sql2却每次都在变化,在cache中,key为整个sql语句,所以每次sql2都无法命中cache,即使它仅仅参数不同,也必须重新检验语法与计算执行计划,效率自然就低下。
强大的数据库会在cache命中上做优化,但复杂的语句还是避免不了miss。
PreparedStatement的存在是为了避免sql2的劣势。看下面code。
String sql2="Select * from test where id=?"; PreparedStatement pstmt = connection.prepareStatement(sql2); pstmt.setInt(1,2); pstmt.executQuery(); pstmt.setInt(1,3); pstmt.executQuery();
PreparedStatement在创建的时候,会将参数化的语句发送给数据库,进行语法检测和执行计划计算。Cache中的key将是参数化的语句。当后面preparedstatement在执行的时候,每次均会命中cache,使用已存在的access plan进行检索。
包含以上优点,PreparedStatement的优点归为:
预编译,节省后面使用的时间。
可防止sql注入。
Statement的生命周期
在了解statement执行过程以后,还需要了解其生命周期。Cache的生命周期。这样我们在设计自己的数据库访问层时,才不会犯自以为正确的错误。
Connection conn = DriverManager.getConnection(...); Statement stmt = conn.createStatement(); stmt.execute(sql); stmt.close(); PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.execute(); pstmt.close(); conn.close();
上面的方法展示了statement的生命周期. 无论是statement还是preparedStatement, 它们都绑定在一个connection上. 调用statement的close方法或connection的close方法,均会导致statement的关闭.
在数据库引擎一端,statement缓存会跟创建它的connection绑定在一起(如果我错了,请纠正我).一旦关闭connection, cache就失效了. 通过下面试验检验这个结论.
在同一个connection下面, 多次执行同一条语句,发现第一次执行时间要长,后面的几次时间相近,但都比第一次要短.
在不同的connection下面,执行同一条语句,发现每次执行时间均很长,且时间相近.
从上述试验,推断出,cache绑定与其connection.
所以,如果只是执行一次语句,那么preparedstatement并不会带来性能优势.只有在保持同一链接的情况下,频繁执行相似的语句,preparedstatement才会带来性能提高.这需要我们在设计ORM框架时要对此注意.
J2EE server下的statement的生命周期
在J2EE应用中,每一个http request均是一个thread处理. 程序员会在DAO层这样写他的代码:
Connection conn = datasource.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.execute(); pstmt.close(); conn.close();
思考一下,在这个thread中,创建了connection, connection又创建了preparedstatement, 最后connection关闭. 倘若每一个http request都这样做, preparedstatement根本没有任何作用. 每次预编译和产生的cache均随着connection的关闭而失效了.
若使preparedstatement能够发挥其优势,则必须让它的生命周期跨越所有的http request线程. J2EE server使用两个功能帮助preparedStatement扩张其生命周期.
Thread Pool
Statement Cache
每一个J2EE服务器均提供thread pool. 它包装了Connection, 使Connection的创建并不是真的创建,只是从pool中拿出一个存在的链接; 而Connection的关闭也只是将它放回pool中,而不是真正关闭.
Statement Cache并不是所有服务器均有的功能, 如tomcat,若想使用statement cache,则需要扩展其API. 此Statement Cache是存在于server JVM堆中的cache, 它缓存了被创建出的preparedStatement. 当调用preparedStatement的关闭方法, 也只是将其放回cache.
如何使用PreparedStatement