perf(distribution): 优化分销订单全量与分站慢查询,消除全表扫描与每行EXISTS子查询

- 数据库迁移:新增 000083 迁移,为 distribution_order_parties 添加 (site_id, order_id) 联合覆盖索引;
- 查询优化:重构 ListOrders,利用覆盖索引派生表预收敛分销订单候选集,替代在 3.7 万行 rental_orders 上的 OR EXISTS 逐行子查询全表扫描;
- 边界修复:超管全量分销订单(site_id = 0)增加分销范围收敛,防止全平台普通非分销订单混杂和 5 表级联扫表;
- 统计优化:同步改造 GetDashboardAnalytics 与 ListDistributionUsers,彻底消除大盘统计与客户画像聚合的慢查询;
- 验证方式:新增 TestListOrdersPerformanceAndScoping 单元测试,全量运行 ./scripts/check.sh 门禁验证通过。
This commit is contained in:
yml2213
2026-10-10 15:17:25 +08:00
parent 4ea08e355b
commit 95435faf46
3 changed files with 203 additions and 21 deletions
@@ -1063,6 +1063,22 @@ func ruleDTO(row model.DistributionRuleVersion) RuleDTO {
return RuleDTO{ID: row.ID, SiteID: row.SiteID, Mode: row.Mode, Version: row.Version, Status: row.Status, PlatformReceiveRatio: row.PlatformReceiveRatio, PlatformSellRatio: row.PlatformSellRatio, DistributorReceiveRatio: row.DistributorReceiveRatio, DistributorSellRatio: row.DistributorSellRatio, DistributorARatio: row.DistributorARatio, DistributorBRatio: row.DistributorBRatio, ServiceFeeValue: row.ServiceFeeValue, ServiceFeeUnit: row.ServiceFeeUnit, PaymentFeeBPS: row.PaymentFeeBPS, TaxEnabled: row.TaxEnabled, TaxRateBPS: row.TaxRateBPS, EffectiveFrom: row.EffectiveFrom, EffectiveTo: row.EffectiveTo, CreatedBy: row.CreatedBy, CreatedAt: row.CreatedAt}
}
const (
siteOrderCandidatesSubquery = "(SELECT id FROM rental_orders WHERE distribution_site_id = ? UNION SELECT order_id AS id FROM distribution_order_parties WHERE site_id = ? OR party_id = ?)"
allDistributionOrderCandidatesSubquery = "(SELECT id FROM rental_orders WHERE distribution_site_id > 0 OR distribution_mode <> '' UNION SELECT order_id AS id FROM distribution_order_parties)"
)
func (r *Repository) joinDistributionOrders(db *gorm.DB, siteID uint64, orderTableAlias string) *gorm.DB {
alias := orderTableAlias
if alias == "" {
alias = "rental_orders"
}
if siteID > 0 {
return db.Joins("JOIN "+siteOrderCandidatesSubquery+" AS matched ON matched.id = "+alias+".id", siteID, siteID, siteID)
}
return db.Joins("JOIN " + allDistributionOrderCandidatesSubquery + " AS matched ON matched.id = " + alias + ".id")
}
func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderListQuery) (*DistributionOrderPage, error) {
page, size := query.Page, query.Size
if page < 1 {
@@ -1074,15 +1090,13 @@ func (r *Repository) ListOrders(ctx context.Context, query DistributionOrderList
if size > 100 {
size = 100
}
base := r.db.WithContext(ctx).Table("rental_orders ro").
base := r.db.WithContext(ctx).Table("rental_orders ro")
base = r.joinDistributionOrders(base, query.SiteID, "ro")
base = base.
Joins("LEFT JOIN distribution_order_financials f ON f.order_id = ro.id").
Joins("LEFT JOIN rental_listings rl ON rl.id = ro.listing_id").
Joins("LEFT JOIN game_accounts ga ON ga.id = ro.account_id").
Joins("LEFT JOIN users u ON u.id = ro.renter_id")
if query.SiteID > 0 {
base = base.Where("(ro.distribution_site_id = ? OR EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = ro.id AND (p.site_id = ? OR p.party_id = ?)))", query.SiteID, query.SiteID, query.SiteID)
}
if query.Status != "" {
base = base.Where("ro.status = ?", query.Status)
}
@@ -3354,14 +3368,17 @@ func (r *Repository) GetDashboardAnalytics(ctx context.Context, adminID, siteID
Where("distribution_site_id = ? AND status = 'published' AND review_status = 'approved'", siteID).Count(&activeListings).Error
var todayOrders int64
_ = r.db.WithContext(ctx).Table("rental_orders ro").
Where("(ro.distribution_site_id = ? OR EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = ro.id AND (p.site_id = ? OR p.party_id = ?))) AND ro.created_at >= ?", siteID, siteID, siteID, todayStr+" 00:00:00").
todayOrdersQ := r.db.WithContext(ctx).Table("rental_orders ro")
todayOrdersQ = r.joinDistributionOrders(todayOrdersQ, siteID, "ro")
_ = todayOrdersQ.Where("ro.created_at >= ?", todayStr+" 00:00:00").
Count(&todayOrders).Error
var todayGMV int64
_ = r.db.WithContext(ctx).Table("rental_orders ro").
todayGMVQ := r.db.WithContext(ctx).Table("rental_orders ro")
todayGMVQ = r.joinDistributionOrders(todayGMVQ, siteID, "ro")
_ = todayGMVQ.
Joins("JOIN distribution_order_financials f ON f.order_id = ro.id").
Where("(ro.distribution_site_id = ? OR EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = ro.id AND (p.site_id = ? OR p.party_id = ?))) AND ro.created_at >= ?", siteID, siteID, siteID, todayStr+" 00:00:00").
Where("ro.created_at >= ?", todayStr+" 00:00:00").
Select("COALESCE(SUM(f.gross_paid_amount_cent), 0)").Scan(&todayGMV).Error
var todayProfit int64
@@ -3383,9 +3400,11 @@ func (r *Repository) GetDashboardAnalytics(ctx context.Context, adminID, siteID
Where("distribution_site_id = ?", siteID).Count(&totalUserCount).Error
var totalOrderUserCount int64
_ = r.db.WithContext(ctx).Table("rental_orders").
Where("(distribution_site_id = ? OR EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = rental_orders.id AND (p.site_id = ? OR p.party_id = ?))) AND status NOT IN ('cancelled', 'closed')", siteID, siteID, siteID).
Distinct("renter_id").Count(&totalOrderUserCount).Error
totalOrderUserQ := r.db.WithContext(ctx).Table("rental_orders ro")
totalOrderUserQ = r.joinDistributionOrders(totalOrderUserQ, siteID, "ro")
_ = totalOrderUserQ.
Where("ro.status NOT IN ('cancelled', 'closed')").
Distinct("ro.renter_id").Count(&totalOrderUserCount).Error
var wallet model.WalletAccount
_ = r.db.WithContext(ctx).Where("user_id = 0 AND distribution_site_id = ?", siteID).First(&wallet).Error
@@ -3401,9 +3420,11 @@ func (r *Repository) GetDashboardAnalytics(ctx context.Context, adminID, siteID
dateFormatExpr := "DATE_FORMAT(ro.created_at, '%Y-%m-%d')"
dateFormatPartyExpr := "DATE_FORMAT(created_at, '%Y-%m-%d')"
selectTrendSQL := fmt.Sprintf("%s AS date_str, COUNT(ro.id) AS orders, COALESCE(SUM(f.gross_paid_amount_cent), 0) AS gmv", dateFormatExpr)
_ = r.db.WithContext(ctx).Table("rental_orders ro").
trendQ := r.db.WithContext(ctx).Table("rental_orders ro")
trendQ = r.joinDistributionOrders(trendQ, siteID, "ro")
_ = trendQ.
Joins("JOIN distribution_order_financials f ON f.order_id = ro.id").
Where("(ro.distribution_site_id = ? OR EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = ro.id AND (p.site_id = ? OR p.party_id = ?))) AND ro.created_at >= ?", siteID, siteID, siteID, startDate+" 00:00:00").
Where("ro.created_at >= ?", startDate+" 00:00:00").
Select(selectTrendSQL).
Group(dateFormatExpr).
Order("date_str ASC").
@@ -3503,14 +3524,18 @@ func (r *Repository) ListDistributionUsers(ctx context.Context, adminID, siteID
_ = r.db.WithContext(ctx).Model(&model.User{}).
Where("distribution_site_id = ?", siteID).Count(&totalUsersCount).Error
_ = r.db.WithContext(ctx).Table("rental_orders").
Where("(distribution_site_id = ? OR EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = rental_orders.id AND (p.site_id = ? OR p.party_id = ?))) AND status NOT IN ('cancelled', 'closed')", siteID, siteID, siteID).
Distinct("renter_id").Count(&orderUsersCount).Error
orderUsersQ := r.db.WithContext(ctx).Table("rental_orders ro")
orderUsersQ = r.joinDistributionOrders(orderUsersQ, siteID, "ro")
_ = orderUsersQ.
Where("ro.status NOT IN ('cancelled', 'closed')").
Distinct("ro.renter_id").Count(&orderUsersCount).Error
orderSubquery := r.db.Table("rental_orders").
Select("renter_id, COUNT(*) AS ord_count, COALESCE(SUM(rent_amount_cent), 0) AS ord_spent, MAX(created_at) AS last_ord_at").
Where("(distribution_site_id = ? OR EXISTS (SELECT 1 FROM distribution_order_parties p WHERE p.order_id = rental_orders.id AND (p.site_id = ? OR p.party_id = ?))) AND status NOT IN ('cancelled', 'closed')", siteID, siteID, siteID).
Group("renter_id")
subq := r.db.Table("rental_orders ro")
subq = r.joinDistributionOrders(subq, siteID, "ro")
orderSubquery := subq.
Select("ro.renter_id, COUNT(*) AS ord_count, COALESCE(SUM(ro.rent_amount_cent), 0) AS ord_spent, MAX(ro.created_at) AS last_ord_at").
Where("ro.status NOT IN ('cancelled', 'closed')").
Group("ro.renter_id")
baseQuery := r.db.WithContext(ctx).Table("users AS u").
Joins("LEFT JOIN (?) AS ord ON ord.renter_id = u.id", orderSubquery)