1. 程式人生 > >Prepared Statements 如何大幅度提高效能

Prepared Statements 如何大幅度提高效能

 
本文講述瞭如何正確的使用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

 

 

http://www.cnblogs.com/meronzhang/archive/2012/09/28/2707374.html