提高订单源的查询速度

This commit is contained in:
wuweihua
2026-10-19 17:44:19 +08:00
parent 383ef55e00
commit 58d7ec90b0
3 changed files with 233 additions and 33 deletions
@@ -22,12 +22,18 @@ public interface OracleUpstreamOrderMapper
long countTabOrderMonitor(@Param("orderTable") String orderTable, @Param("query") TabOrderMonitorQuery query); long countTabOrderMonitor(@Param("orderTable") String orderTable, @Param("query") TabOrderMonitorQuery query);
List<Map<String, Object>> selectTabOrderMonitorPage( /** Key-first page for customer+time queries (sort narrow columns only). */
List<Map<String, Object>> selectTabOrderMonitorKeyPage(
@Param("orderTable") String orderTable, @Param("orderTable") String orderTable,
@Param("query") TabOrderMonitorQuery query, @Param("query") TabOrderMonitorQuery query,
@Param("startRow") long startRow, @Param("startRow") long startRow,
@Param("endRow") long endRow); @Param("endRow") long endRow);
/** List detail by ids: display columns only. */
List<Map<String, Object>> selectTabOrderMonitorListByIds(
@Param("orderTable") String orderTable,
@Param("ids") List<String> ids);
/** Distinct product codes for product-vs-package check (grouped, no pagination). */ /** Distinct product codes for product-vs-package check (grouped, no pagination). */
List<Map<String, Object>> selectTabOrderProductCodes( List<Map<String, Object>> selectTabOrderProductCodes(
@Param("orderTable") String orderTable, @Param("orderTable") String orderTable,
@@ -61,6 +61,12 @@ public class TabOrderMonitorService
/** 收集结果缓存条目上限(LRU 淘汰),避免不同单号集合无限占内存。 */ /** 收集结果缓存条目上限(LRU 淘汰),避免不同单号集合无限占内存。 */
private static final int COLLECT_CACHE_MAX_ENTRIES = 20; private static final int COLLECT_CACHE_MAX_ENTRIES = 20;
/**
* 客户+时间列表:一次拉取的主键上限。一次取齐后内存分页,总数稳定;
* 超过此数量仅展示前 N 条(避免 COUNT / 翻页估总数导致「共 101→201」)。
*/
private static final int FILTER_LIST_KEY_LIMIT = 2000;
/** 单批 Oracle 查询最长等待(从库 socketTimeout 为 120s,留同样余量)。 */ /** 单批 Oracle 查询最长等待(从库 socketTimeout 为 120s,留同样余量)。 */
private static final long COLLECT_BATCH_TIMEOUT_SECONDS = 120L; private static final long COLLECT_BATCH_TIMEOUT_SECONDS = 120L;
@@ -102,6 +108,19 @@ public class TabOrderMonitorService
} }
}); });
/** 客户+时间列表主键缓存(与单号收集缓存分开,存 ID 列表)。 */
private final Map<String, FilterIdCacheEntry> filterIdCache =
Collections.synchronizedMap(new LinkedHashMap<String, FilterIdCacheEntry>(16, 0.75f, true)
{
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(Map.Entry<String, FilterIdCacheEntry> eldest)
{
return size() > COLLECT_CACHE_MAX_ENTRIES;
}
});
private static final class CacheEntry private static final class CacheEntry
{ {
private final List<MatchedOrderKey> keys; private final List<MatchedOrderKey> keys;
@@ -114,6 +133,18 @@ public class TabOrderMonitorService
} }
} }
private static final class FilterIdCacheEntry
{
private final List<String> ids;
private final long expireAt;
private FilterIdCacheEntry(List<String> ids, long expireAt)
{
this.ids = ids;
this.expireAt = expireAt;
}
}
@PreDestroy @PreDestroy
public void shutdownCollectPool() public void shutdownCollectPool()
{ {
@@ -253,21 +284,8 @@ public class TabOrderMonitorService
Long.valueOf(System.currentTimeMillis() - t0)); Long.valueOf(System.currentTimeMillis() - t0));
return new TabOrderPageResult(keys.size(), rows); return new TabOrderPageResult(keys.size(), rows);
} }
long total; // Customer+time: collect keys once (capped), memory-page; stable total (no 101->201).
try return doSelectFilterListPage(table, query, pn, ps);
{
PageHelper.clearPage();
total = oracleUpstreamOrderMapper.countTabOrderMonitor(table, query);
}
catch (RuntimeException e)
{
throw new ServiceException("Oracle TAB_ORDER count failed: " + e.getMessage());
}
finally
{
PageHelper.clearPage();
}
return new TabOrderPageResult(total, doSelectRownumPage(table, query, pn, ps));
} }
@DataSource(DataSourceType.SLAVE) @DataSource(DataSourceType.SLAVE)
@@ -338,27 +356,103 @@ public class TabOrderMonitorService
private List<TabOrderMonitorVo> doSelectRownumPage(String table, TabOrderMonitorQuery query, int pn, int ps) private List<TabOrderMonitorVo> doSelectRownumPage(String table, TabOrderMonitorQuery query, int pn, int ps)
{ {
return toVoList(doSelectRownumRows(table, query, pn, ps)); return doSelectFilterListPage(table, query, pn, ps).getRows();
}
/**
* Customer+time list: pull up to {@link #FILTER_LIST_KEY_LIMIT} keys once (cached),
* then memory-page and load display rows by ID. Total stays stable across pages.
*/
private TabOrderPageResult doSelectFilterListPage(String table, TabOrderMonitorQuery query, int pn, int ps)
{
long t0 = System.currentTimeMillis();
List<String> allIds = doCollectFilterKeyIds(table, query);
int total = allIds.size();
int from = (pn - 1) * ps;
if (from >= total)
{
log.info("oracle tab_order filter-list customerSize={} begin={} end={} page={}/{} rows=0 total={} costMs={}",
query.getCustomerCodes() == null ? 0 : query.getCustomerCodes().size(),
query.getBeginTime(), query.getEndTime(), pn, ps, Integer.valueOf(total),
Long.valueOf(System.currentTimeMillis() - t0));
return new TabOrderPageResult(total, Collections.emptyList());
}
List<String> slice = allIds.subList(from, Math.min(from + ps, total));
List<TabOrderMonitorVo> list = loadListVosByIds(table, new ArrayList<String>(slice));
log.info("oracle tab_order filter-list customerSize={} begin={} end={} page={}/{} rows={} total={} costMs={}",
query.getCustomerCodes() == null ? 0 : query.getCustomerCodes().size(),
query.getBeginTime(), query.getEndTime(), pn, ps,
Integer.valueOf(list.size()), Integer.valueOf(total),
Long.valueOf(System.currentTimeMillis() - t0));
return new TabOrderPageResult(total, list);
}
/** Collect (and cache) filter-path primary keys, newest first, capped at FILTER_LIST_KEY_LIMIT. */
private List<String> doCollectFilterKeyIds(String table, TabOrderMonitorQuery query)
{
String fingerprint = "filter|" + collectFingerprint(table, query);
FilterIdCacheEntry cached = filterIdCache.get(fingerprint);
long now = System.currentTimeMillis();
if (cached != null && cached.expireAt > now)
{
return new ArrayList<String>(cached.ids);
}
// Fetch limit+1 to detect cap; keep at most FILTER_LIST_KEY_LIMIT.
List<String> ids = selectKeyIds(table, query, 0L, FILTER_LIST_KEY_LIMIT + 1L);
if (ids.size() > FILTER_LIST_KEY_LIMIT)
{
log.warn("oracle tab_order filter-list capped at {} keys (customer+time window too large); narrow pickup range",
Integer.valueOf(FILTER_LIST_KEY_LIMIT));
ids = new ArrayList<String>(ids.subList(0, FILTER_LIST_KEY_LIMIT));
}
filterIdCache.put(fingerprint, new FilterIdCacheEntry(Collections.unmodifiableList(ids),
now + COLLECT_CACHE_TTL_MS));
return new ArrayList<String>(ids);
} }
private List<TabOrderRow> doSelectRownumRows(String table, TabOrderMonitorQuery query, int pn, int ps) private List<TabOrderRow> doSelectRownumRows(String table, TabOrderMonitorQuery query, int pn, int ps)
{ {
long endRow = (long) pn * ps; long endRow = (long) pn * ps;
long startRow = endRow - ps; long startRow = endRow - ps;
long t0 = System.currentTimeMillis();
List<String> ids = selectKeyIds(table, query, startRow, endRow);
List<TabOrderRow> rows = loadFullRowsByIds(table, ids);
log.info("oracle tab_order sync-page customerSize={} begin={} end={} page={}/{} rows={} costMs={}",
query.getCustomerCodes() == null ? 0 : query.getCustomerCodes().size(),
query.getBeginTime(), query.getEndTime(), pn, ps,
Integer.valueOf(rows.size()), Long.valueOf(System.currentTimeMillis() - t0));
return rows;
}
private List<String> selectKeyIds(String table, TabOrderMonitorQuery query, long startRow, long endRow)
{
try try
{ {
PageHelper.clearPage(); PageHelper.clearPage();
long t0 = System.currentTimeMillis(); List<Map<String, Object>> keys =
List<Map<String, Object>> rows = oracleUpstreamOrderMapper.selectTabOrderMonitorPage(table, query, startRow, endRow); oracleUpstreamOrderMapper.selectTabOrderMonitorKeyPage(table, query, startRow, endRow);
log.info("oracle tab_order page customerSize={} begin={} end={} page={}/{} rows={} costMs={}", if (keys == null || keys.isEmpty())
query.getCustomerCodes() == null ? 0 : query.getCustomerCodes().size(), {
query.getBeginTime(), query.getEndTime(), pn, ps, return Collections.emptyList();
rows == null ? 0 : rows.size(), System.currentTimeMillis() - t0); }
return wrapRows(rows); List<String> ids = new ArrayList<String>(keys.size());
for (Map<String, Object> key : keys)
{
Object idVal = value(key, "ID");
if (idVal != null)
{
String id = String.valueOf(idVal).trim();
if (!id.isEmpty())
{
ids.add(id);
}
}
}
return ids;
} }
catch (RuntimeException e) catch (RuntimeException e)
{ {
throw new ServiceException("Oracle TAB_ORDER list failed: " + e.getMessage()); throw new ServiceException("Oracle TAB_ORDER key page failed: " + e.getMessage());
} }
finally finally
{ {
@@ -366,6 +460,88 @@ public class TabOrderMonitorService
} }
} }
private List<TabOrderMonitorVo> loadListVosByIds(String table, List<String> ids)
{
if (ids == null || ids.isEmpty())
{
return Collections.emptyList();
}
try
{
PageHelper.clearPage();
List<Map<String, Object>> rows = oracleUpstreamOrderMapper.selectTabOrderMonitorListByIds(table, ids);
Map<String, Map<String, Object>> rowById = indexRowsById(rows);
List<TabOrderMonitorVo> list = new ArrayList<TabOrderMonitorVo>(ids.size());
for (String id : ids)
{
Map<String, Object> row = rowById.get(id);
if (row != null)
{
list.add(toVo(row));
}
}
return list;
}
catch (RuntimeException e)
{
throw new ServiceException("Oracle TAB_ORDER list by ids failed: " + e.getMessage());
}
finally
{
PageHelper.clearPage();
}
}
private List<TabOrderRow> loadFullRowsByIds(String table, List<String> ids)
{
if (ids == null || ids.isEmpty())
{
return Collections.emptyList();
}
try
{
PageHelper.clearPage();
List<Map<String, Object>> rows = oracleUpstreamOrderMapper.selectTabOrderMonitorByIds(table, ids);
Map<String, Map<String, Object>> rowById = indexRowsById(rows);
List<Map<String, Object>> ordered = new ArrayList<Map<String, Object>>(ids.size());
for (String id : ids)
{
Map<String, Object> row = rowById.get(id);
if (row != null)
{
ordered.add(row);
}
}
return wrapRows(ordered);
}
catch (RuntimeException e)
{
throw new ServiceException("Oracle TAB_ORDER list by ids failed: " + e.getMessage());
}
finally
{
PageHelper.clearPage();
}
}
private static Map<String, Map<String, Object>> indexRowsById(List<Map<String, Object>> rows)
{
Map<String, Map<String, Object>> rowById = new LinkedHashMap<String, Map<String, Object>>();
if (rows == null)
{
return rowById;
}
for (Map<String, Object> row : rows)
{
Object idVal = value(row, "ID");
if (idVal != null)
{
rowById.put(String.valueOf(idVal).trim(), row);
}
}
return rowById;
}
private static List<TabOrderRow> wrapRows(List<Map<String, Object>> rows) private static List<TabOrderRow> wrapRows(List<Map<String, Object>> rows)
{ {
if (rows == null || rows.isEmpty()) if (rows == null || rows.isEmpty())
@@ -46,21 +46,25 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select> </select>
<!-- 除大批量单号外的所有过滤条件(大批量单号走分批收集 selectMatchedOrderKeys) --> <!-- 除大批量单号外的所有过滤条件(大批量单号走分批收集 selectMatchedOrderKeys) -->
<!-- Customer + pickup first so CBO can use those indexes on month-range queries. -->
<sql id="tabOrderFilterWhere"> <sql id="tabOrderFilterWhere">
o.BL_PICKUP = 1
AND o.ORDER_STATUS IN (3, 4, 5, 6, 7, 8, 11)
<if test="query.customerCodes != null and query.customerCodes.size() > 0"> <if test="query.customerCodes != null and query.customerCodes.size() > 0">
AND o.CUSTOMER_CODE IN o.CUSTOMER_CODE IN
<foreach collection="query.customerCodes" item="code" open="(" separator="," close=")"> <foreach collection="query.customerCodes" item="code" open="(" separator="," close=")">
#{code} #{code}
</foreach> </foreach>
</if> </if>
<if test="query.customerCodes == null or query.customerCodes.size() == 0">
1 = 1
</if>
<if test="query.beginTime != null and query.beginTime != ''"> <if test="query.beginTime != null and query.beginTime != ''">
AND o.PICKUP_DATE &gt;= TO_TIMESTAMP(#{query.beginTime}, 'YYYY-MM-DD HH24:MI:SS') AND o.PICKUP_DATE &gt;= TO_TIMESTAMP(#{query.beginTime}, 'YYYY-MM-DD HH24:MI:SS')
</if> </if>
<if test="query.endTime != null and query.endTime != ''"> <if test="query.endTime != null and query.endTime != ''">
AND o.PICKUP_DATE &lt;= TO_TIMESTAMP(#{query.endTime}, 'YYYY-MM-DD HH24:MI:SS') AND o.PICKUP_DATE &lt;= TO_TIMESTAMP(#{query.endTime}, 'YYYY-MM-DD HH24:MI:SS')
</if> </if>
AND o.BL_PICKUP = 1
AND o.ORDER_STATUS IN (3, 4, 5, 6, 7, 8, 11)
<if test="query.orderId != null and query.orderId != ''"> <if test="query.orderId != null and query.orderId != ''">
AND o.ORDER_ID = #{query.orderId} AND o.ORDER_ID = #{query.orderId}
</if> </if>
@@ -128,11 +132,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="tabOrderMonitorWhere"/> <include refid="tabOrderMonitorWhere"/>
</select> </select>
<select id="selectTabOrderMonitorPage" resultType="java.util.HashMap"> <!--
SELECT * FROM ( Month-range list: sort only narrow keys, then Java loads display/full rows by ID.
SELECT inner_q.*, ROWNUM AS rn FROM ( Avoids ORDER BY on o.* which gets very slow when pickup window is ~1 month.
<!-- 同步计费需要全行数据(toBillRequest 读取数十列),故取 o.* 而非展示列 --> -->
SELECT o.* <select id="selectTabOrderMonitorKeyPage" resultType="java.util.HashMap">
SELECT ID FROM (
SELECT inner_q.ID AS ID, ROWNUM AS rn FROM (
SELECT /*+ FIRST_ROWS(${endRow}) */ o.ID, o.PICKUP_DATE, o.ORDER_ID
FROM ${orderTable} o FROM ${orderTable} o
WHERE WHERE
<include refid="tabOrderMonitorWhere"/> <include refid="tabOrderMonitorWhere"/>
@@ -143,6 +150,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
WHERE rn &gt; #{startRow} WHERE rn &gt; #{startRow}
</select> </select>
<!-- List detail by primary keys (display columns only). -->
<select id="selectTabOrderMonitorListByIds" resultType="java.util.HashMap">
SELECT
<include refid="tabOrderMonitorColumns"/>
FROM ${orderTable} o
WHERE o.ID IN
<foreach collection="ids" item="mid" open="(" separator="," close=")">
#{mid}
</foreach>
</select>
<!-- Distinct product codes under the same filters (no pagination). --> <!-- Distinct product codes under the same filters (no pagination). -->
<select id="selectTabOrderProductCodes" resultType="java.util.HashMap"> <select id="selectTabOrderProductCodes" resultType="java.util.HashMap">
SELECT o.CUSTOMER_CODE AS CUSTOMER_CODE, SELECT o.CUSTOMER_CODE AS CUSTOMER_CODE,