zoukankan      html  css  js  c++  java
  • PreparedStatement是如何大幅度提高性能的

    本文讲述了如何正确的使用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,你的应用程序会具有更好的性能。

     ///////////////

    1.PreparedStatement是预编译的,对于批量处理可以大大提高效率. 也叫JDBC存储过程 
    2.使用 Statement 对象。在对数据库只执行一次性存取的时侯,用 Statement 对象进行处理。PreparedStatement 对象的开销比Statement大,对于一次性操作并不会带来额外的好处。 
    3.statement每次执行sql语句,相关数据库都要执行sql语句的编译,preparedstatement是预编译得,preparedstatement支持批处理 
    4. Code Fragment 1: 

    String updateString = "UPDATE COFFEES SET SALES = 75 " + "WHERE COF_NAME LIKE ′Colombian′"; 
    stmt.executeUpdate(updateString); 

    Code Fragment 2: 

    PreparedStatement updateSales = con.prepareStatement("UPDATE COFFEES SET SALES = ? WHERE COF_NAME LIKE ? "); 
    updateSales.setInt(1, 75); 
    updateSales.setString(2, "Colombian"); 
    updateSales.executeUpdate(); 

    片断2和片断1的区别在于,后者使用了PreparedStatement对象,而前者是普通的Statement对象。 PreparedStatement对象不仅包含了SQL语句,而且大多数情况下这个语句已经被预编译过,因而当其执行时,只需DBMS运行SQL语句, 而不必先编译。当你需要执行Statement对象多次的时候,PreparedStatement对象将会大大降低运行时间,当然也加快了访问数据库的 速度。 
    这种转换也给你带来很大的便利,不必重复SQL语句的句法,而只需更改其中变量的值,便可重新执行SQL语句。选择PreparedStatement对 象与否,在于相同句法的SQL语句是否执行了多次,而且两次之间的差别仅仅是变量的不同。如果仅仅执行了一次的话,它应该和普通的对象毫无差异,体现不出 它预编译的优越性。 
    5.执行许多SQL语句的JDBC程序产生大量的Statement和PreparedStatement对象。通常认为 PreparedStatement对象比Statement对象更有效,特别是如果带有不同参数的同一SQL语句被多次执行的时候。 PreparedStatement对象允许数据库预编译SQL语句,这样在随后的运行中可以节省时间并增加代码的可读性。 

    然而,在Oracle环境中,开发人员实际上有更大的灵活性。当使用Statement或PreparedStatement对象时,Oracle数据库 会缓存(和JDBC缓存是两回事)SQL语句以便以后使用。在一些情况下,由于驱动器自身需要额外的处理和在Java应用程序和Oracle服务器间增加的网络活动,执行 PreparedStatement对象实际上会花更长的时间(因此,使用statement的最好时机是频繁的参数--是参数项目而不是参数的值--变化时的SQL语句)。 

    然而,除了缓冲的问题之外,至少还有一个更好的原因使我们在企业应用程序中更喜欢使用PreparedStatement对象,那就是安全性。传递给 PreparedStatement对象的参数可以被强制进行类型转换,使开发人员可以确保在插入或查询数据时与底层的数据库格式匹配。 

    当处理公共Web站点上的用户传来的数据的时候,安全性的问题就变得极为重要。传递给PreparedStatement的字符串参数会自动被驱动器忽 略。最简单的情况下,这就意味着当你的程序试着将字符串“D'Angelo”插入到VARCHAR2中时,该语句将不会识别第一个“,”,从而导致悲惨的 失败。几乎很少有必要创建你自己的字符串忽略代码。 

    在Web环境中,有恶意的用户会利用那些设计不完善的、不能正确处理字符串的应用程序。特别是在公共Web站点上,在没有首先通过 PreparedStatement对象处理的情况下,所有的用户输入都不应该传递给SQL语句。此外,在用户有机会修改SQL语句的地方,如HTML的 隐藏区域或一个查询字符串上,SQL语句都不应该被显示出来。 
    在执行SQL命令时,我们有二种选择:可以使用PreparedStatement对象,也可以使用Statement对象。无论多少次地使用同一个 SQL命令,PreparedStatement都只对它解析和编译一次。当使用Statement对象时,每次执行一个SQL命令时,都会对它进行解析 和编译。 


    第一: 

    prepareStatement会先初始化SQL,先把这个SQL提交到数据库中进行预处理,多次使用可提高效率。 
    createStatement不会初始化,没有预处理,没次都是从0开始执行SQL 

    第二: 

    prepareStatement可以替换变量 
    在SQL语句中可以包含?,可以用ps=conn.prepareStatement("select * from Cust where ID=?"); 
    int sid=1001; 
    ps.setInt(1, sid); 
    rs = ps.executeQuery(); 
    可以把?替换成变量。 
    而Statement只能用 int sid=1001; 
    Statement stmt = conn.createStatement(); 
    ResultSet rs = stmt.executeQuery("select * from Cust where ID="+sid); 
    来实现。 

    第三: 

    prepareStatement会先初始化SQL,先把这个SQL提交到数据库中进行预处理,多次使用可提高效率。 
    createStatement不会初始化,没有预处理,没次都是从0开始执行SQL

  • 相关阅读:
    fzuoj Problem 2177 ytaaa
    zoj The 12th Zhejiang Provincial Collegiate Programming Contest Capture the Flag
    zoj The 12th Zhejiang Provincial Collegiate Programming Contest Team Formation
    zoj The 12th Zhejiang Provincial Collegiate Programming Contest Beauty of Array
    zoj The 12th Zhejiang Provincial Collegiate Programming Contest Lunch Time
    zoj The 12th Zhejiang Provincial Collegiate Programming Contest Convert QWERTY to Dvorak
    zoj The 12th Zhejiang Provincial Collegiate Programming Contest May Day Holiday
    zoj The 12th Zhejiang Provincial Collegiate Programming Contest Demacia of the Ancients
    zjuoj The 12th Zhejiang Provincial Collegiate Programming Contest Ace of Aces
    csuoj 1335: 高桥和低桥
  • 原文地址:https://www.cnblogs.com/onmyway20xx/p/4273332.html
Copyright © 2011-2022 走看看