提高订单源的查询速度
This commit is contained in:
+7
-1
@@ -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,
|
||||||
|
|||||||
+200
-24
@@ -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 >= TO_TIMESTAMP(#{query.beginTime}, 'YYYY-MM-DD HH24:MI:SS')
|
AND o.PICKUP_DATE >= 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 <= TO_TIMESTAMP(#{query.endTime}, 'YYYY-MM-DD HH24:MI:SS')
|
AND o.PICKUP_DATE <= 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 > #{startRow}
|
WHERE rn > #{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,
|
||||||
|
|||||||
Reference in New Issue
Block a user