1. 問題
最近在專案中修bug的時候,碰到這樣一個錯誤:
Caused by: java.sql.SQLException:Prepared or callable statement has more than 2000 parameter markers. at net.sourceforge.jtds.jdbc.SQLParser.parse(SQLParser.java:1139) at net.sourceforge.jtds.jdbc.SQLParser.parse(SQLParser.java:156) at net.sourceforge.jtds.jdbc.JtdsPreparedStatement.<init>(JtdsPreparedStatement.java:107) Caused by: java.sql.SQLException:Prepared or callable statement has more than 2000 parameter markers.
出錯的程式碼簡化如下(list中的元素個數可能大於2000):
1 SELECT * FROM xxx x 2 WHERE x.date <= :today AND x.id IN (:list)
2. 收穫
起初並不明白是什麼情況,經過一番google加請教之後,有一些收穫。專案中用的JTDS的driver版本為1.2.4,資料庫為SQL Server。在該driver的原始碼中找到如下一個片段(在net.sourceforge.jtds.jdbc.SQLParser類中):
從這段程式碼中可以看出,其實這並不是一個bug,而是work as designed。我們得到的另一個資訊是,對於不同型別的資料庫,不同的版本之間,這種約束是有差別的,分別是255,1000,2000。這樣看來,之前的查詢語句出錯也就很好理解了,在IN子句中的引數個數多於2000是肯定會拋異常的。
3. 解決方案
主要有兩種解決方案:一種是優化SQL語句,一種是優化查詢邏輯(即分批處理)。
對於第一種方案,如果IN子句中的引數來自於另一張表的話,可以考慮類似如下的查詢方式:
1 SELECT * FROM xxx x 2 INNER JOIN yyy y ON x.id = y.id 3 WHERE x.date <= :today
對於第二種方案,分批來查詢速度沒有第一種快,但是不用修改SQL語句。
1 List<Item> result = Lists.newArrayList(); 2 List<List<String>> partitionIds = Lists.partition(ids, BATCH_SIZE); 3 for (List<String> partitionId : partitionIds) { 4 param.put("list", partitionId); 5 List<Item> resultItems = xxxService.query(param); 6 result.addAll(resultItems); 7 } 8 return result;
這種方案的劣勢在於速度上會有損失,但是可以從Java程式碼的邏輯層避免上面的問題。
希望這些對大家有所幫助。