如何使用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.

相關文章
相關標籤/搜索