您現在的位置是:首頁 > 單機遊戲首頁單機遊戲

JDBC 批處理 Select 語句

簡介但不幸的是對於批次查詢,JDBC並沒有內建(built-in)的方法,而且JDBC執行批處理的時候也不能有SELECT語句,如:Statement pstmt = conn

分頁快取池怎麼減少

注:為了更好理解本文,請結合原文閱讀

在網路上開銷最昂貴的資源就是客戶端與伺服器往返的請求與響應,JDBC中類似的一種情況就是對資料庫的呼叫,如果你在做資料插入、更新、刪除操作,你可以使用executeBatch()方法減少資料庫呼叫次數,如:

Statement pstmt = conn。createStatement();pstmt。addBatch(“insert into settings values(3,‘liu’)”);pstmt。addBatch(“insert into settings values(4,‘zhi’)”);pstmt。addBatch(“insert into settings values(5,‘jun’)”);pstmt。executeBatch();

但不幸的是對於批次查詢,JDBC並沒有內建(built-in)的方法,而且JDBC執行批處理的時候也不能有SELECT語句,如:

Statement pstmt = conn。createStatement();pstmt。addBatch(“select * from settings”);pstmt。executeBatch();

會丟擲異常:

Exception in thread “main” java。sql。BatchUpdateException: Can not issue SELECT via executeUpdate()。 at com。mysql。jdbc。Statement。executeBatch(Statement。java:961) at test。SelectBatchTest。test2(SelectBatchTest。java:49) at test。SelectBatchTest。main(SelectBatchTest。java:12)

假設你想從一系列指定的id列表中獲取名字,邏輯上,我們要做的事情看起來應該是:

PreparedStatement stmt = conn。prepareStatement( “select id, name from users where id in (?)”);stmt。setString(“1,2,3”);

但是這樣做並不能得到預期的結果,JDBC只允許你用單個的字面值來替換“?” JDBC之所以這麼做是有必要的,因為如果SQL自身可以改變的話,JDBC驅動就沒法預編譯SQL語句了,另一方面它還能防止SQL注入攻擊。

但有四種可替代的實現方法可供選擇:

分別對每個id做查詢

一個查詢做完所有事

使用儲存過程

選擇批處理

方法一: 分別對每個id做查詢

假設有100個id,那麼就有100次資料庫呼叫:

PreparedStatement stmt = conn。prepareStatement( “select id, name from users where id = ?”);for ( int i=0; i < 100; i++ ) { stmt。setInt(i); // or whatever values you are trying to query by // execute statement and get result}

這種方法寫起來非常簡單,但是效能非常慢,資料庫往返要處理100次。

方法二:一個查詢完成所有事

在執行時,你可以使用一個迴圈來構建如下SQL語句:

PreparedStatement stmt = conn。prepareStatement( “select id, name from users where id in (?, ?, ?)”);stmt。setInt(1);stmt。setInt(2);stmt。setInt(3);

這種方案從程式碼相比第一種方法算是第二簡單的,它解決了來回多次請求資料庫的問題,但是如果每次請求引數的個數不一樣時預處理語句就必須重新編譯,由於每次SQL字面值不匹配,因此如果分別用10個id、3個id、100個,這樣會在快取中產生三個預處理語句。除了重新編譯預處理語句之外,先前快取池中的預處理語句將被移除(受限於快取池大小),進而導致重新編譯已編譯過的語句。最後,這種查詢方式在記憶體溢位或磁碟分頁操作時查詢會佔用很長時間。

該方案的另一種變體就是在SQL語句中硬編碼:

PreparedStatement stmt = conn。prepareStatement(“select id, name from users where id in (1, 2, 3)”);

這樣方式甚至更差,而且沒有任何機會對SQL語句重用,至少用“?”還可以對使用相同數量引數的SQL語句進行重用。

PreparedStatement stmt = conn。prepareStatement( “select id, name from users where id in (?) ; ” + “select id, name from users where id in (?); ” + “select id, name from users where id in (?)”);stmt。setInt(1);stmt。setInt(2);stmt。setInt(3);

這種方法的優點就是每次查詢模版語句都一樣,資料庫不需要每次計算執行路徑。然而,從資料庫驅動的角度來說SQL每次都不一樣,預處理語句每次必須預處理儲存在快取中。而且不是所有資料庫系統都支援分號間隔的多個SQL語句的

方法三:使用儲存過程

儲存過程執行在資料庫系統中,因此它可以做很多查詢而不需要太多網路負載,儲存過程可以收集所有結果一次性返回。這是一種速度很快的解決方案。但是它對資料庫的依賴比較強,不能隨意的切換資料庫系統,否則需要重寫儲存過程而且需要你分離應用伺服器與資料庫伺服器之間的邏輯。如果應用架構已經使用了儲存過程,無疑這是隻最佳方案。

方法四:批次查詢

批次查詢是方案一和方案二的折衷選擇,它預先確定一批查詢引數的常量,然後用這些引數構建一批查詢。因為這隻會涉及到有限個查詢,所以它有預處理語句的優勢(預編譯不會與快取中的預處理發生碰撞)。批處理多個值在相同的查詢保留了伺服器來回請求最小化的優勢。最後你可以透過控制批處理的上限來避免大查詢的記憶體問題。如果你有很關鍵的查詢對效能方面有要求又不想用儲存過程,那麼這是一種很好的解決辦法,現在我們透過一個例子說明:

public static final int SINGLE_BATCH = 1;public static final int SMALL_BATCH = 4;public static final int MEDIUM_BATCH = 11;public static final int LARGE_BATCH = 51;

第一件要做的事是你要衡量有多少批處理以及每個批處理的大小。(注意:在真實的程式碼中,這些值應該寫在一個配置檔案中而不是採取硬編碼的形式,也就是說,你可以在執行時試驗並改變批處理的大小)不管真正的批處理大小是多大,你總需要一個單個的批處理——-大小為1的批處理(SINGLE_BATTCH)。這樣如果有人請求的就是一個值或者在一個很大的查詢中最後有遺留下來的單個值都能派上用場。對於批處理的大小,使用素數會更好些。換句話說,大小不應該可以相互的整除或者被相同的數整除。請求數的最大值將有最少的伺服器往返。批處理的大小的數量和真正的大小是基於配置變化的。需要注意的是:大的批處理大小不應該太大否則你將遇到記憶體麻煩。同時最小批處理的大小應該很小,你可能會使用這個來做很多次的查詢。

while ( totalNumberOfValuesLeftToBatch > 0 ) {

按如下方式重複操作直到推出迴圈。

int batchSize = SINGLE_BATCH;if ( totalNumberOfValuesLeftToBatch >= LARGE_BATCH ) { batchSize = LARGE_BATCH;} else if ( totalNumberOfValuesLeftToBatch >= MEDIUM_BATCH ) { batchSize = MEDIUM_BATCH;} else if ( totalNumberOfValuesLeftToBatch >= SMALL_BATCH ) { batchSize = SMALL_BATCH;}totalNumberOfValuesLeftToBatch -= batchSize;

這種方案在這裡是查詢到最大的批處理大小,可能這個最大值比我們實際要查詢的值稍大。舉例說明:假設查詢有75個引數,那麼首先選擇51個元素(LARGE_BATCH),現在還剩24個待查詢,然後接著用11個元素的查詢(MEDIUM_BATCH)。現在還有13個值,因為仍然大於11,再做一次11個元素的查詢,現在只剩下2個值,它少於那個最小的批處理4(SMALL_BATCH),所以做兩次單查詢。總共5次往返用了3次預處理在快取中。這是一個很重要的改進比單獨地坐75次單查詢。

StringBuilder inClause = new StringBuilder();boolean firstValue = true;for (int i=0; i < batchSize; i++) { inClause。append(‘?’); if ( firstValue ) { firstValue = false; } else { inClause。append(‘,’); }}PreparedStatement stmt = conn。prepareStatement( “select id, name from users where id in (” + inClause。toString() + ‘)’);

現在已經構建了一個真實的預處理語句,由於一直用相同的方式構建的查詢,驅動注意到SQL是相同的。(注意:如果你還沒有用Java5,使用StringBuffer替換StringBuilder才能正常編譯),返回id很重要這樣有利於查詢哪個名字對應哪個id。

for (int i=0; i < batchSize; i++) { stmt。setInt(i); // or whatever values you are trying to query by}

設定合適的值數量去查詢,包括其他搜尋條件查詢。僅僅只要把這些引數在之舉引數之後。在這種情況你可以最終當前的索引。

從這點來看,你僅僅只是執行查詢返回了結果,在第一次嘗試的時候,你應該關注一下效能的提升,根據具體情況調整最佳化批處理的大小(batch size)。

正如那句名言所說:“過早的最佳化是萬惡之源”,批處理應該是用於解決效能問題。

原文:

Batching Select Statements in JDBCBatching Select Statements in JDBC

Top