利用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