1. 程式人生 > >利用jdbc操作資料庫——prepareStatement和Statement的比較以及利用batch模式提高效率的心得

利用jdbc操作資料庫——prepareStatement和Statement的比較以及利用batch模式提高效率的心得

1.prepareStatement   vs  statement

(1)prepareStatement預編譯SQL語句,批處理效率高

什麼是預編譯,好處?(參考https://blog.csdn.net/Marvel__Dead/article/details/69486947) 當客戶傳送一條SQL語句給伺服器後,伺服器總是需要校驗SQL語句的語法格式是否正確,然後把SQL語句編譯成可執行的函式,最後才是執行SQL語句。其中校驗語法,和編譯所花的時間可能比執行SQL語句花的時間還要多。 注意:可執行函式儲存在MySQL伺服器中,並且當前連線斷開後,MySQL伺服器會清除已經儲存的可執行函式。 如果我們需要執行多次insert語句,但只是每次插入的值不同,MySQL伺服器也是需要每次都去校驗SQL語句的語法格式,以及編譯,這就浪費了太多的時間。如果使用預編譯功能,那麼只對SQL語句進行一次語法校驗和編譯,所以效率要高。

看個例子: statement:

stmt = conn.createStatement(); //迴圈 for(){     stmt.executeUpdate(sql); }

prepareStatement:

ps = conn.prepareStatement(sql); //迴圈 for(){     ps.setString(1,"");     ps.setString(2,"");     //...     ps.executeUpdate(); }

對於prepareStatement,SQL 語句被預編譯並存儲在 PreparedStatement 物件中 prepareStatement只需初始化時傳語句引數,之後利用setXXXX方法傳參即可 對於Statement,批量處理SQL語句時,每次都需要校驗語法,然後編譯

(2)prepareStatement避免了拼接語句,程式碼可讀性好 Statement寫sql語句有時會因為欄位值本身存在單引號而出錯 prepareStatement利用的賦值方法可有效避免而且程式碼簡潔,易於維護

(3)prepareStatement防止sql注入,安全性好 sql注入就是利用sql語句的漏洞來入侵,舉個場景 在登入時,需要輸入使用者名稱和密碼,經Statement拼接sql語句去資料庫中查詢校驗 當用戶輸入使用者名稱為:‘ or true or ’時,拼接成的sql語句為:

select * from user where name = ' ' or true or ' ' and password = ' '

仍可以正常登入,存在安全問題。但prepareStatement採用set方法傳參,就不會出現這種問題

(4)注意: 如果只是執行單次查詢語句,不涉及批量處理時,可以使用statement,筆者測試時,statement速度略快於prepareStatement,後者第一次執行的消耗還是不小的。

2.prepareStatement  vs  prepareStatement with batch

資料庫操作提高效率,核心在於減少資料庫驅動與資料庫之間的網路請求,batch模式則可以批量的提交sql語句,減少網路請求。而且batchSize的大小對速度也有影響 示例程式碼如下:

conn.setAutoCommit(false); for(int i=0;...){     ps.setXXX()     ps.addBatch();     if(i%batchSize==0){         ps.executeBatch();         conn.commit();     } } ps.executeBatch(); conn.commit();

注意:使用batch時,一定要設定setAutoCommit(false),採用手動提交事務,否則經筆者測試,只加入batch在效率提升上效果甚微。 setAutoCommit(true)(預設):sql命令的提交(commit)由驅動程式負責,預設一條SQL就是一個單獨事務,執行完自動提交,無法回滾事務  setAutoCommit(false):sql命令的提交由應用程式負責,程式必須呼叫commit或者rollback方法 除了提高效率,手動提交事務則可以充分利用事務回滾能力,當想把多個操作作為一個整體執行時,設定為false,通過try/catch捕獲異常,然後手動進行事務回滾,充分體現了事務的原子性

重構的程式碼如下:

try {     conn.setAutoCommit(false);     if(ps!=null){         ps.clearParameters();         ps.clearBatch();     }     ps = conn.prepareStatement(sql);     //TODO     /*for(int i=0;i<t.size();i++){         ps.setString(1,"");         ps.setString(2,"");         ps.addBatch();         if(i%batchSize==0){             ps.executeBatch();             conn.commit();         }     }*/     ps.executeBatch();     conn.commit(); } catch (SQLException e) {     try {         conn.rollback();         System.out.println("更新出錯,事務已回滾!");     } catch (SQLException e1) {         System.out.println("更新出錯,事務未回滾!");         e1.printStackTrace();     }     e.printStackTrace(); } finally {     closeDB();     System.out.println("連線關閉!"); }

3.prepareStatement with batch  vs  prepareStatement with batch + rewriteBatchedStatements=true

在資料庫url引數中加入“rewriteBatchedStatements=true”,示例如下:

String dbUrl =  "jdbc:mysql://localhost:3306/User?useUnicode=true&characterEncoding =utf-8&rewriteBatchedStatements

=true";

下面這段引用自:https://blog.csdn.net/tolcf/article/details/52102849#commentBox

MySQL Jdbc驅動在預設情況下會無視executeBatch()語句,把我們期望批量執行的一組sql語句拆散,一條一條地發給MySQL資料庫

當rewriteBatchedStatements=true後,對delete和update,驅動所做的事就是把多條sql語句累積起來再一次性發出去; 例如:delete from t where id = 1; delete from t where id = 2; delete from t where id = 3 update t set … where id = 1; update t set … where id = 2; update t set … where id = 3 而對於insert,驅動則會把多條sql語句重寫成一條sql語句,然後再發出去 例如:insert into t (…) values (…) , (…), (…)

需要注意的是,當batchSize <= 3時,驅動會一條一條地執行SQL,所以針對較大的batch會有提升效果

總結: (1)對於單次查詢,可以使用Statement

(2)對於批量操作(尤其是insert時),使用prepareStatement效率高,經測試,最優組合為 prepareStatement+batch+setAutoCommit(false)+rewriteBatchedStatements=true