PreparedStatement是如何大幅度提高性能的

 PreparedStatement是如何大幅度提高性能的

 
作者: Billy Newport
 
本文讲述了如何正确的使用 prepared statements 。为什么它可以让你的应用程序运行的更快,和同样的让数据库操作变的更快。
 
 
为什么 Prepared Statements 非常重要?如何正确的使用它?
 
数据库有着非常艰苦的工作。它们接受来自众多并发的客户端所发出的 SQL 查询,并尽可能快的执行查询并返回结果。处理 statements 是一个开销昂贵的操作,不过现在有了 Prepared Statements 这样的方法,可以将这种开销降到最低。可是这种优化需要开发者来完成。所以本文会为大家展示如何正确的使用 Prepared Statements 才能使数据库操作达到最优化。
 
 
数据库是如何执行一个 statement 的?
 
显然,我不会在这里写出很多的细节,我们只关注最关键的部分。当一个数据库收到一个 statement 后,数据库引擎会先解析 statement ,然后检查其是否有语法错误。一旦 statement 被正确的解析,数据库会选出执行 statement 的最优途径。遗憾的是这个计算开销非常昂贵。数据库会首先检查是否有相关的索引可以对此提供帮助,不管是否会将一个表中的全部行都读出来。数据库对数据进行统计,然后选出最优途径。当决创建查询方案后,数据库引擎会将它执行。
 
存取方案( Access Plan )的生成会占用相当多的 CPU 。理想的情况是,当我们多次发送一个 statement 到数据库,数据库应该对 statement 的存取方案进行重用。如果方案曾经被生成过的话,这将减少 CPU 的使用率。
 
 
Statement Caches
 
数据库已经具有了类似的功能。它们通常会用如下方法对 statement 进行缓存。使用 statement 本身作为 key 并将存取方案存入与 statement 对应的缓存中。这样数据库引擎就可以对曾经执行过的 statements 中的存取方案进行重用。举个例子,如果我们发送一条包含 SELECT a, b FROM t WHERE c = 2 的 statement 到数据库,然后首先会将存取方案进行缓存。当我们再次发送相同的 statement 时,数据库会对先前使用过的存取方案进行重用,这样就降低了 CPU 的开销。
 
注意,这里使用了整个 statement 为 key 。也就是说,如果我们发送一个包含 SELECT a, b FROM t WHERE c = 3 的 statement 的话,缓存中不会没有与之对应的存取方案。这是因为“ c=3 ”与曾经被缓存过的“ c=2 ”不同。所以,举个例子:
 
for (int i = 0; i < 1000; i++)  {
PreparedStatement ps = conn.prepareStatement("select a,b from t where c = " + i);
ResultSet rs = Ps.executeQuery();
rs.close();
ps.close();
}
 
在这里缓存不会被使用,因为每一次迭代都会发送一条包含不同 SQL 语句的 statement 给数据库。并且每一次迭代都会生成一个新的存取方案。现在让我们来看看下一段代码:
 
PreparedStatement ps = conn.prepareStatement("select a,b from t where c = ?");
for (int i = 0; i < 1000; i++)  {
ps.setInt(1, i);
ResultSet rs = ps.executeQuery();
rs.close();
ps.close();
}
 
这样就具有了更好的效率,这个 statement 发送给数据库的是一条带有参数“?”的 SQL 语句。这样每次迭代会发送相同的 statement 到数据库,只是参数“ c=? ”不同。这种方法允许数据库重用 statement 的存取方案,这样就具有了更好的效率。这可以让你的应用程序速度更快,并且使用更少的 CPU ,这样数据库服务器就可以为更多的人提供服务。
 
 
PreparedStatement J2EE 服务器
 
当我们使用 J2EE 服务器时事情会变的比较复杂。通常,一个 perpared statement 会同一个单独的数据库连接相关联。当数据库连接被关闭时 prepared statement 也会被丢弃。通常,一个胖客户端会获取一个数据库连接并将其一直保持到退出。它会用“饿汉” (eagerly) 或“懒汉” (lazily) 方式创建所有的 parepared statements 。“饿汉”方式会在应用启动时创建一切。“懒汉”方式意味着只有在使用的时候才去创建。“饿汉”方式会使应用程序在启动的时候梢有延迟,但一旦启动后就会运行的相当理想。“懒汉”方式使应用程序启动速度非常快(但不会做任何准备工作),当需要使用 prepared statement 的时候再去创建。这样,在创建全部 statement 的过程中,性能是非常不稳定的,但一旦创建了所有 statement 后,它会像“饿汉”式应用程序一样具有很好的运行效果。请根据你的需要来选择最好的方式,是快速启动?还是前后一致的性能。
 
J2EE 应用的问题是它不会像这样工作,连接只会在请求期间被保持。那意味着必须每一次请求的时候都创建 prepared statement 。这远没有胖客户端那种一直保持 prepared statement 的执行性能好。 J2EE 厂商已经注意到了这个问题,并且提供了连接池 (ConnectionPool) 以避免这种问题。
 
当 J2EE 服务器提供了一个连接给你的应用程序时,其实它并没有给你真正的数据库连接,你只是获得了一个包装器( Wrapper )。你可以去看看你所获得的连接的类名以证实这一点。它并不是一个 JDBC 连接,而是一个由应用服务器创建的类。所有的 JDBC 操作都会被应用服务器的连接池管理器所代理。所有的 JDBC ResultSets , statements , CallableStatements , preparedStatements 等都会被包装并以一个“代理对象”( Proxy Object )的形式返回给应用程序。当你关闭了连接,这些对象会被标记为失效,并被垃圾回收器所回收。
 
通常,如果你对一个数据库连接执行 close ,那这个连接会被 JDBC 驱动程序关闭。但我们需要在 J2EE 服务器执行 close 的时候数据库连接会被返回连接池。我们可以创建一个像真正的连接一样的 JDBC Connection 代理类来解决这个问题。它有一个对真正连接的引用。当我们执行一个连接上的方法时,代理会将操作转给真正的连接。但是,当我们对一个连接执行 close 时,这个连接并不会关闭,而是会送回连接池,并可以被其他请求所使用。一个已被准备过的 prepared statement 也会因此而得到重用。
 
 
J2EE PreparedStatement Cache
 
J2EE 服务器的连接池管理器已经实现了缓存的使用。 J2EE 服务器保持着连接池中每一个连接准备过的 prepared statement 列表。当我们在一个连接上调用 preparedStatement 时,应用服务器会检查这个 statement 是否曾经准备过。如果是,这个 PreparedStatement 会被返回给应用程序。如果否,调用会被转给 JDBC 驱动程序,然后将新生成的 statement 对象存入连接缓存。
 
每个连接都有一个缓存的原因是因为: JDBC 驱动程序就是这样工作的。任何 prepared statement 都是由指定的连接所返回的。
 
如果我们想利用这个缓存的优势,那就如前面所说的,使用参数化的查询语句可以在缓存中找到曾经使用过的 statement 。大部分应用服务器允许你调整 prepared statements 缓存的大小。
 
 
摘要
 
我们绝对应该使用包含参数化的查询语句的 prepared statement 。这样数据库就会重用准备过的存取方案。缓存适用于整个数据库,所以,如果你安排所有的应用程序使用相同的参数化 SQL 语句,然后你的其他应用程序就可以重用被准备过的 prepared statement 。这是应用服务器的一个优势,因为所有的数据库操作都集中在数据库操作层( Database Access Layer ,包括 O/R 映射,实体 Bean , JDBC 等)。
 
第二,正确的使用 prepared statement 也是利用 prepared statement 的缓存优势的关键。由于应用程序可以重用准备过的 prepared statement ,也就减少了调用 JDBC 驱动程序的次数,从而提高了应用程序的性能。这样就拥有了可以与胖客户端比肩的效率,却又不需要总维持一个连接。
 
使用参数化的 prepared statement ,你的应用程序会具有更好的性能。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值