Please indicate the source when reprinting: https://blog.csdn.net/men_ma/article/details/106847165.
This article comes from the blog of Little Yuanyuan who is not afraid of reporting errors but is afraid of not reporting errors.
When an error is reported Exception: Too many optimizations applied to query plan. Current limit 10000, solution to too many SQL placeholders
- reason:
-
- The original query SQL is as follows:
- Solution:
- The last modified SQL:
Reason:
When I have the same interface and there are no problems in the test and UAT environment, the following error is reported when I go to production.
Code: 572. DB::Exception: Too many optimizations applied to query plan. Current limit 10000. (TOO_MANY_QUERY_PLAN_OPTIMIZATIONS) (version 23.4.2.11 (official build))\\ "
The original query SQL is as follows:
<select id="getStoreLevelCompareTable" resultType="com.datastory.gac.domain.vo.store.StoreLevelCompareVO"> SELECT test.storeCode, max(test.flowNum) AS flowNum, max(test.flowNumDay) AS flowNumDay, max(test.avgRetentionDay) AS avgRetentionDay, max(test.flowNumBatchDay) AS flowNumBatchDay FROM ( SELECT store_code AS storeCode, SUM(count) AS flowNum, 0 AS flowNumDay, 0 AS avgRetentionDay, 0 AS flowNumBatchDay FROM gac_customer_flow hd <if test="storeHours != null and storeHours.size > 0"> left join <foreach collection="storeHours" item="item" open="(" close=")" separator="union all"> select #{<!-- -->item.storeCode} as store_code, #{<!-- -->item.startTime} as startTime, #{<!-- -->item.endTime} as endTime </foreach> as temp on hd.store_code = temp.store_code </if> <where> <if test="storeHours != null and storeHours.size > 0"> hd.public_time >= toDate(hd.public_time) + toIntervalSecond(temp.startTime) and hd.public_time & amp;lt; toDate(hd.public_time) + toIntervalSecond(temp.endTime) </if> <if test="storeCodes != null and storeCodes.size > 0"> and hd.store_code in <foreach collection="storeCodes" item="item" separator="," open="(" close=")"> #{<!-- -->item} </foreach> </if> and toDate(hd.public_time) BETWEEN #{<!-- -->startTime} and #{<!-- -->endTime} AND hd.data_type = 0 </where> GROUP BY hd.store_code having toUInt64(flowNum)>0 UNION ALL SELECT store_code AS storeCode, 0 AS flowNum, round( SUM ( timeTotalNum ) / COUNT ( * ) ) AS flowNumDay, 0 AS avgRetentionDay, 0 AS flowNumBatchDay FROM ( SELECT toDate(hd.public_time) AS timeFrame, store_code, SUM(count) AS timeTotalNum FROM gac_customer_flow hd <if test="storeHours != null and storeHours.size > 0"> left join <foreach collection="storeHours" item="item" open="(" close=")" separator="union all"> select #{<!-- -->item.storeCode} as store_code, #{<!-- -->item.startTime} as startTime, #{<!-- -->item.endTime} as endTime </foreach> as temp on hd.store_code = temp.store_code </if> <where> <if test="storeHours != null and storeHours.size > 0"> hd.public_time >= toDate(hd.public_time) + toIntervalSecond(temp.startTime) and hd.public_time & amp;lt; toDate(hd.public_time) + toIntervalSecond(temp.endTime) </if> <if test="storeCodes != null and storeCodes.size > 0"> and hd.store_code in <foreach collection="storeCodes" item="item" separator="," open="(" close=")"> #{<!-- -->item} </foreach> </if> and toDate(hd.public_time) BETWEEN #{<!-- -->startTime} and #{<!-- -->endTime} AND hd.data_type = 0 </where> GROUP BY toDate(hd.public_time), hd.store_code having timeTotalNum>0 ) AS tmp GROUP BY store_code UNION ALL select temp.store_code AS storeCode, 0 AS flowNum, 0 AS flowNumDay, round( ( SUM ( temp.value ) / COUNT ( * ) ) / 60, 2 ) AS avgRetentionDay, 0 AS flowNumBatchDay from ( SELECT store_code, gsar.public_time AS xAxisName, SUM(gsar.avg_retention) as value FROM gac_store_avg_retention gsar <where> gsar.public_time BETWEEN #{<!-- -->startTime} and #{<!-- -->endTime} <if test="storeCodes != null and storeCodes.size > 0"> and gsar.store_code in <foreach collection="storeCodes" item="item" separator="," open="(" close=")"> #{<!-- -->item} </foreach> </if> </where> GROUP BY store_code, xAxisName )temp group by temp.store_code UNION ALL SELECT store_code AS storeCode, 0 AS flowNum, 0 AS flowNumDay, 0 AS avgRetentionDay, round(SUM(personBatchNum) / COUNT(*)) AS flowNumBatchDay FROM ( select hd.batch_id, hd.store_code, toDate ( hd.stu_event_time ) as timeFrame, COUNT( DISTINCT hd.batch_id ) AS personBatchNum from gac_batch_customer_flow hd left join <foreach collection="storeHours" item="item" open="(" close=")" separator="union all"> select #{<!-- -->item.storeCode} as store_code, #{<!-- -->item.startTime} as startTime, #{<!-- -->item.endTime} as endTime </foreach> as temp on hd.store_code = temp.store_code <where> <if test="storeHours != null and storeHours.size > 0"> hd.stu_event_time >= toDate(hd.stu_event_time) + toIntervalSecond(temp.startTime) and hd.stu_event_time & amp;lt; toDate(hd.stu_event_time) + toIntervalSecond(temp.endTime) </if> <if test="storeCodes != null and storeCodes.size > 0"> and hd.store_code in <foreach collection="storeCodes" item="item" separator="," open="(" close=")"> #{<!-- -->item} </foreach> </if> and toDate(hd.stu_event_time) BETWEEN #{<!-- -->startTime} and #{<!-- -->endTime} </where> GROUP BY hd.batch_id,hd.store_code,toDate(hd.stu_event_time) having personBatchNum>0 ) AS tmp GROUP BY store_code ) test GROUP BY test.storeCode <if test="ascOrDesc==1 and fieldName=='flowNum'"> order by flowNum asc </if> <if test="ascOrDesc==0 and fieldName=='flowNum'"> order by flowNum desc </if> <if test="ascOrDesc==1 and fieldName=='flowNumDay'"> order by flowNumDay asc </if> <if test="ascOrDesc==0 and fieldName=='flowNumDay'"> order by flowNumDay desc </if> <if test="ascOrDesc==1 and fieldName=='avgRetentionDay'"> order by avgRetentionDay asc </if> <if test="ascOrDesc==0 and fieldName=='avgRetentionDay'"> order by avgRetentionDay desc </if> <if test="ascOrDesc==1 and fieldName=='flowNumBatchDay'"> order by flowNumBatchDay asc </if> <if test="ascOrDesc==0 and fieldName=='flowNumBatchDay'"> order by flowNumBatchDay desc </if> limit 20 </select>
Since the same list parameter is used in multiple subqueries and a lot of foreach is used, the log is also very large when running SQL? (Placeholder) causes the SQL to be too long. For example: there are 700 or 800 business hours data in the storeHours collection, and there are more than 300 stores in storeCodes. Among them, I also used UNION ALL to perform a joint query, which is just a subquery. There are thousands of placeholders and four or five subqueries. The placeholders are directly flushed in the log, and the performance of the database will not support it.
Solution:
Use Java to splice the parameters in the storeHours collection with direct parameters, and then use a temporary table to store the data. When using the temporary table, perform a joint query based on the store number, so that each subquery will not have a large number of placeholders.
The storeCodes collection is also spliced directly using java, using ${} SQL injection instead of #{}.
The code processing is as follows:
//Splicing business hours SQL: Avoiding errors caused by too many placeholders in SQL traversal StringBuilder sql = new StringBuilder(); if (ToolUtil.isNotEmpty(storeHours)) {<!-- --> for (int i = 0; i < storeHours.size(); i + + ) {<!-- --> StoreHourDto storeHour = storeHours.get(i); sql.append("SELECT '").append(storeHour.getStoreCode()).append("' AS storeCode,") .append(storeHour.getStartTime()).append(" AS startTime,") .append(storeHour.getEndTime()).append(" AS endTime"); if (i < storeHours.size() - 1) {<!-- --> sql.append(" UNION ALL "); } } } //Splice store numbers to avoid errors caused by too many placeholders in traversal StringBuilder storeSql = new StringBuilder(); List<String> storeCodes = levelCompareDto.getStoreCodes(); String storeCodeString=null; if (ToolUtil.isNotEmpty(storeCodes)){<!-- --> for (int i = 0; i < storeCodes.size(); i + + ) {<!-- --> storeSql.append("'" + storeCodes.get(i) + "'"); if (i < storeCodes.size() - 1) {<!-- --> storeSql.append(", "); } } storeCodeString=storeSql.toString(); }
Last modified SQL:
<select id="getStoreLevelCompareTable" resultType="com.datastory.gac.domain.vo.store.StoreLevelCompareVO"> WITH StoreHourseTable AS ( SELECT item.storeCode as store_code, item.startTime, item.endTime FROM ( ${<!-- -->storeHourSql} ) as item ) SELECT test.storeCode, max(test.flowNum) AS flowNum, max(test.flowNumDay) AS flowNumDay, max(test.avgRetentionDay) AS avgRetentionDay, max(test.flowNumBatchDay) AS flowNumBatchDay FROM ( SELECT store_code AS storeCode, SUM(count) AS flowNum, 0 AS flowNumDay, 0 AS avgRetentionDay, 0 AS flowNumBatchDay FROM gac_customer_flow hd <if test="storeHours != null and storeHours.size > 0"> left join StoreHourseTable as temp on hd.store_code = temp.store_code </if> <where> <if test="storeHours != null and storeHours.size > 0"> hd.public_time >= toDate(hd.public_time) + toIntervalSecond(temp.startTime) and hd.public_time & amp;lt; toDate(hd.public_time) + toIntervalSecond(temp.endTime) </if> <if test="storeCodes != null and storeCodes != ''"> and hd.store_code in (${<!-- -->storeCodes}) </if> and toDate(hd.public_time) BETWEEN #{<!-- -->startTime} and #{<!-- -->endTime} AND hd.data_type = 0 </where> GROUP BY hd.store_code having toUInt64(flowNum)>0 UNION ALL SELECT store_code AS storeCode, 0 AS flowNum, round( SUM ( timeTotalNum ) / COUNT ( * ) ) AS flowNumDay, 0 AS avgRetentionDay, 0 AS flowNumBatchDay FROM ( SELECT toDate(hd.public_time) AS timeFrame, store_code, SUM(count) AS timeTotalNum FROM gac_customer_flow hd <if test="storeHours != null and storeHours.size > 0"> left join StoreHourseTable as temp on hd.store_code = temp.store_code </if> <where> <if test="storeHours != null and storeHours.size > 0"> hd.public_time >= toDate(hd.public_time) + toIntervalSecond(temp.startTime) and hd.public_time & amp;lt; toDate(hd.public_time) + toIntervalSecond(temp.endTime) </if> <if test="storeCodes != null and storeCodes != ''"> and hd.store_code in (${<!-- -->storeCodes}) </if> and toDate(hd.public_time) BETWEEN #{<!-- -->startTime} and #{<!-- -->endTime} AND hd.data_type = 0 </where> GROUP BY toDate(hd.public_time), hd.store_code having timeTotalNum>0 ) AS tmp GROUP BY store_code UNION ALL select temp.store_code AS storeCode, 0 AS flowNum, 0 AS flowNumDay, round( ( SUM ( temp.value ) / COUNT ( * ) ) / 60, 2 ) AS avgRetentionDay, 0 AS flowNumBatchDay from ( SELECT store_code, gsar.public_time AS xAxisName, SUM(gsar.avg_retention) as value FROM gac_store_avg_retention gsar <where> gsar.public_time BETWEEN #{<!-- -->startTime} and #{<!-- -->endTime} <if test="storeCodes != null and storeCodes != ''"> and gsar.store_code in (${<!-- -->storeCodes}) </if> </where> GROUP BY store_code, xAxisName )temp group by temp.store_code UNION ALL SELECT store_code AS storeCode, 0 AS flowNum, 0 AS flowNumDay, 0 AS avgRetentionDay, round(SUM(personBatchNum) / COUNT(*)) AS flowNumBatchDay FROM ( select hd.batch_id, hd.store_code, toDate ( hd.stu_event_time ) as timeFrame, COUNT( DISTINCT hd.batch_id ) AS personBatchNum from gac_batch_customer_flow hd <if test="storeHours != null and storeHours.size > 0"> left join StoreHourseTable as temp on hd.store_code = temp.store_code </if> <where> <if test="storeHours != null and storeHours.size > 0"> hd.stu_event_time >= toDate(hd.stu_event_time) + toIntervalSecond(temp.startTime) and hd.stu_event_time & amp;lt; toDate(hd.stu_event_time) + toIntervalSecond(temp.endTime) </if> <if test="storeCodes != null and storeCodes != ''"> and hd.store_code in (${<!-- -->storeCodes}) </if> and toDate(hd.stu_event_time) BETWEEN #{<!-- -->startTime} and #{<!-- -->endTime} </where> GROUP BY hd.batch_id,hd.store_code,toDate(hd.stu_event_time) having personBatchNum>0 ) AS tmp GROUP BY store_code ) test GROUP BY test.storeCode <if test="ascOrDesc==1 and fieldName=='flowNum'"> order by flowNum asc </if> <if test="ascOrDesc==0 and fieldName=='flowNum'"> order by flowNum desc </if> <if test="ascOrDesc==1 and fieldName=='flowNumDay'"> order by flowNumDay asc </if> <if test="ascOrDesc==0 and fieldName=='flowNumDay'"> order by flowNumDay desc </if> <if test="ascOrDesc==1 and fieldName=='avgRetentionDay'"> order by avgRetentionDay asc </if> <if test="ascOrDesc==0 and fieldName=='avgRetentionDay'"> order by avgRetentionDay desc </if> <if test="ascOrDesc==1 and fieldName=='flowNumBatchDay'"> order by flowNumBatchDay asc </if> <if test="ascOrDesc==0 and fieldName=='flowNumBatchDay'"> order by flowNumBatchDay desc </if> limit 20 </select>
If you have any better solutions, please leave a message in the comment area!