diff --git a/apps/backend/src/db/migrations/113_cost_center_query_indexes.sql b/apps/backend/src/db/migrations/113_cost_center_query_indexes.sql new file mode 100644 index 00000000..ed5bf6d1 --- /dev/null +++ b/apps/backend/src/db/migrations/113_cost_center_query_indexes.sql @@ -0,0 +1,19 @@ +-- 113_cost_center_query_indexes.sql +-- 成本中心商品页性能:总览的「未映射订单项」与商品列表的 affiliate 任务聚合。 +-- +-- 背景:测试库 40 万 order_items 时,总览对 canonical_product_id IS NULL 做全表扫描约 3.8s; +-- 商品列表在人工 Tab 仍会聚合 30 天 affiliate 任务,测试机 IO 差时超过 10s。 +-- 两个索引都是部分索引,体积远小于全表索引。 + +CREATE INDEX IF NOT EXISTS idx_order_items_unmapped_mode + ON order_items (fulfillment_mode) + WHERE canonical_product_id IS NULL; + +CREATE INDEX IF NOT EXISTS idx_fulfillment_tasks_affiliate_recent + ON fulfillment_tasks (created_at) + WHERE executor_key = 'affiliate_dash'; + +COMMENT ON INDEX idx_order_items_unmapped_mode IS + '成本中心总览:按履约方式统计未归一订单项,避免全表扫 order_items'; +COMMENT ON INDEX idx_fulfillment_tasks_affiliate_recent IS + '自动发货任务 30 天聚合:只索引 affiliate_dash,减少商品列表聚合扫描'; diff --git a/apps/backend/src/services/cost-center/product-catalog-service.ts b/apps/backend/src/services/cost-center/product-catalog-service.ts index e9da9556..cf9f8f90 100644 --- a/apps/backend/src/services/cost-center/product-catalog-service.ts +++ b/apps/backend/src/services/cost-center/product-catalog-service.ts @@ -106,6 +106,14 @@ export async function listAdminProducts(payload: JsonObject = {}) { baseClauses.push(productSourceFilterSql(baseParams.length)) } + // 人工/接单平台商品不需要 affiliate 任务统计:跳过 30 天 fulfillment_tasks 聚合。 + // 只有「全部」和「自动发货」Tab 才需要这条聚合,避免小库 IO 被无关数据拖慢。 + const needsAffiliateTaskStats = !source || source === 'affiliate_dash' + const affiliateTaskJoinSql = needsAffiliateTaskStats ? AFFILIATE_TASK_AGG_JOIN : '' + const affiliateColumnsSql = needsAffiliateTaskStats + ? 'COALESCE(aff.task_count, 0) AS affiliate_task_count, aff.last_fulfilled_at AS affiliate_last_fulfilled_at' + : '0::int AS affiliate_task_count, NULL::timestamptz AS affiliate_last_fulfilled_at' + const categoryId = normalizeCategoryFilter(payload.categoryId ?? payload.category_id) const rootCategoryId = normalizeCategoryFilter(payload.rootCategoryId ?? payload.root_category_id) const listParams = [...baseParams] @@ -196,11 +204,10 @@ export async function listAdminProducts(payload: JsonObject = {}) { LIMIT 1 ) ) AS active_cost_effective_from, - COALESCE(aff.task_count, 0) AS affiliate_task_count, - aff.last_fulfilled_at AS affiliate_last_fulfilled_at + ${affiliateColumnsSql} FROM product_catalog p ${PRODUCT_CATEGORY_JOINS} - ${AFFILIATE_TASK_AGG_JOIN} + ${affiliateTaskJoinSql} ${where} ORDER BY p.status ASC,