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 = 2statement到數據庫,然後首先會將存取方案進行緩存。當我們再次發送相同的statement時,數據庫會對先前使用過的存取方案進行重用,這樣就降低了CPU的開銷。
 
注意,這裏使用了整個statementkey。也就是說,如果我們發送一個包含SELECT a, b FROM t WHERE c = 3statement的話,緩存中不會沒有與之對應的存取方案。這是因爲“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,這樣數據庫服務器就可以爲更多的人提供服務。
 
 
PreparedStatementJ2EE服務器
 
當我們使用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 ResultSetsstatementsCallableStatementspreparedStatements等都會被包裝並以一個“代理對象”(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映射,實體BeanJDBC等)。
 
第二,正確的使用prepared statement也是利用prepared statement的緩存優勢的關鍵。由於應用程序可以重用準備過的prepared statement,也就減少了調用JDBC驅動程序的次數,從而提高了應用程序的性能。這樣就擁有了可以與胖客戶端比肩的效率,卻又不需要總維持一個連接。
 
使用參數化的prepared statement,你的應用程序會具有更好的性能。
發佈了195 篇原創文章 · 獲贊 1 · 訪問量 55萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章