故事
今天下午公司技術分享,一個夥伴提到他踩過坑:mybatis批量插入時動態sql允許的最大參數數量是2100個。即下面代碼中“#{…}”的數量。
<insert id="batchInsert" parameterType="list"> insert into Adv_permeability values <foreach collection="permeabilityList" separator="," item="permeability"> (#{permeability.areaId}, #{permeability.areaName}, #{permeability.flag}) </foreach> </insert> |
當時就有一個小夥伴不開心了,他明確表示自己曾經成功插入超過2100個參數,並當場拉出了代碼證明之。由此猜測mybatis
批量插入的上限是按對象數量限制的,不是參數,雙方爭執不下,一片歡樂。
探究
mybatis
真的對動態 SQL 的 參數 或者 對象 數量設定了限制麼?如果有那麼原因何在?如果沒有,那麼爲什麼雙方都那麼確定呢?難道是 數據庫 不同導致的?
本着最小投入的原則,筆者開始了測試。手頭現有 SQLServer
和 Mysql
就測這兩個吧。腳本使用上述的簡單腳本。
SQLServer
對 SQLServer
進行2000次批量插入,實體參數是3個,即本次插入參數數量是 2000*3=6000,結果是 報錯 了!
com.microsoft.sqlserver.jdbc.SQLServerException: 傳入的請求具有過多的參數。該服務器支持最多 2100 個參數。請減少參數的數目,然後重新發送該請求。
可以清楚看到,錯誤是 SQLServer
的JDBC包拋出的。
不過,筆者記得在用 SqlServer Management Studio
批量插入式,最大條數有 1000 的限制, 那麼在用 Mybatis
時還會有麼?這裏將插入參數變爲一個 {permeability.areaId}
,插入 1315 條,結果 報錯了!
com.microsoft.sqlserver.jdbc.SQLServerException: INSERT 語句中行值表達式的數目超出了 1000 行值的最大允許值。
果然,是 SQLServer
數據庫自己的鬼,不是 Mybatis
的鍋!
Mysql
對 Mysql
進行同樣的2000次批量插入,結果是完美執行。筆者一咬牙,來了次 200000 的親密接觸,結果 報錯 了!
com.mysql.jdbc.PacketTooBigException: Packet for query is too large (8346602 > 4194304). You can change this value on the server by setting the max_allowed_packet’ variable.
看來 Mysql
也有限制,跟 SQLServer
“數參數個數” 的限制方法不同, Mysql
對執行的SQL語句大小進行限制,相當於對字符串進行限制。根據報錯內容,默認允許最大SQL是 4M 。這個錯誤是 Mysql
的JDBC包拋出的。
可以使用語句查詢這個值並改變:
# 查詢當前大小 mysql> select @@max_allowed_packet; +----------------------+ | @@max_allowed_packet | +----------------------+ | 4194304 | +----------------------+ 1 row in set (0.00 sec) # 修改爲256M mysql> SET GLOBAL max_allowed_packet=268435456; Query OK, 0 rows affected (0.00 sec) # 退出客戶端重新登錄 mysql> select @@max_allowed_packet; +----------------------+ | @@max_allowed_packet | +----------------------+ | 268435456 | +----------------------+ 1 row in set (0.00 sec) |
Mybatis
簡單看一下 Mybatis
解析動態SQL的源碼。
// 開始解析 public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingChacheRefs(); parsePendingStatements(); } // 解析mapper private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } } // 創建 select|insert|update|delete 語句 private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } } // 填充參數,創建語句 public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } return boundSql; } |
從開始到結束, Mybatis
都沒有對填充的條數和參數的數量做限制。
結語
SqlServer
對語句的條數和參數的數量都有限制,分別是 1000 和 2100。Mysql
對語句的長度有限制,默認是 4M。Mybatis
對動態語句沒有數量上的限制。