如何使用PreparedStatement

Statement执行过程

一个sql语句执行过程当中,将经历这么几个步骤:
java

  1. 传输sql到数据库。程序员

  2. 数据库检查sql的语法合法性,并解析sql。面试

  3. 计算Access Plan。数据库会经过检测index,statistics来给出最优的访问计划。sql

  4. 根据访问计划进行检索,返回数据。数据库

在上面步骤中,第1,2,3步是都是耗时的。所以,为了提升性能,数据库会缓存执行语句以及其部分Access Plan, 而后生成一个句柄, 将句柄返回给客户端。缓存的部分被称为statement cache。当客户端使用PreparedStatement的时候, 只须要调用此句柄, 使用二进制发送相应的参数, 而不须要再发送整个sql语句了,数据库会使用缓存中的access plan以节省cpu时间。
缓存

相比较statement, 时间节省在:tomcat

  1. 网络传输, statement使用asicii编码传输整个sql语句, 而preparedstatement使用二进制传输句柄和参数, 流量变小不少.服务器

  2. 不用再编译sql网络

  3. 可使用已经有的部分优化.框架

看下面一段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的优势归为:

  1. 预编译,节省后面使用的时间。

  2. 可防止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扩张其生命周期.

  1. Thread Pool

  2. Statement Cache

每个J2EE服务器均提供thread pool. 它包装了Connection, 使Connection的建立并非真的建立,只是从pool中拿出一个存在的连接; 而Connection的关闭也只是将它放回pool中,而不是真正关闭.

Statement Cache并非全部服务器均有的功能, 如tomcat,若想使用statement cache,则须要扩展其API. 此Statement Cache是存在于server JVM堆中的cache, 它缓存了被建立出的preparedStatement. 当调用preparedStatement的关闭方法, 也只是将其放回cache.

相关文章
相关标签/搜索