fix: 提醒模块两个性能问题

1. list_reminders 从内存分页改为 SQL LIMIT/OFFLOAD 分页
2. list_inactive_customers 从 N+1 逐个查询改为批量查询+内存聚合

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
wsb1224 2026-07-04 13:44:10 +08:00
parent 0bda0bbaa0
commit 4c12149953
2 changed files with 75 additions and 36 deletions

View File

@ -22,30 +22,37 @@ class ReminderRepository:
ReminderService提醒服务调用
"""
def list_reminders(self, session: Session, filters: dict) -> list[SystemReminder]:
"""查询系统提醒列表
def list_reminders(self, session: Session, filters: dict) -> tuple[list[SystemReminder], int]:
"""查询系统提醒列表SQL 分页)
根据提醒类型状态接收人等条件过滤提醒记录
id 倒序返回
id 倒序返回当前页数据及总条数
参数:
session: 数据库会话
filters: 过滤条件字典支持 reminder_type / status / receiver_user_id
filters: 过滤条件字典支持 reminder_type / status / receiver_user_id / page_no / page_size
返回:
符合条件的系统提醒列表
(当前页系统提醒列表, 总条数) 元组
"""
stmt = select(SystemReminder)
base = select(SystemReminder)
if filters.get("reminder_type"):
stmt = stmt.where(SystemReminder.reminder_type == filters["reminder_type"])
base = base.where(SystemReminder.reminder_type == filters["reminder_type"])
if filters.get("status"):
stmt = stmt.where(SystemReminder.status == filters["status"])
base = base.where(SystemReminder.status == filters["status"])
if filters.get("receiver_user_id") is not None:
stmt = stmt.where(SystemReminder.receiver_user_id == filters["receiver_user_id"])
base = base.where(SystemReminder.receiver_user_id == filters["receiver_user_id"])
stmt = stmt.order_by(SystemReminder.id.desc())
return list(session.execute(stmt).scalars())
count_stmt = select(func.count()).select_from(base.subquery())
total = session.execute(count_stmt).scalar() or 0
page_no = filters.get("page_no", 1)
page_size = filters.get("page_size", 20)
offset = max(page_no - 1, 0) * page_size
stmt = base.order_by(SystemReminder.id.desc()).offset(offset).limit(page_size)
rows = list(session.execute(stmt).scalars())
return rows, total
def get_reminder(self, session: Session, reminder_id: int) -> SystemReminder | None:
"""根据 ID 查询单条系统提醒
@ -364,29 +371,63 @@ class ReminderRepository:
.order_by(Customer.id.asc())
).scalars()
)
if not customers:
return []
customer_ids = [c.id for c in customers]
# 批量查累计合同金额(替代逐个 N+1
amount_rows = session.execute(
select(
SalesOrder.customer_id,
func.coalesce(func.sum(SalesOrder.contract_amount), 0).label("total_amount"),
)
.where(
SalesOrder.deleted == 0,
SalesOrder.customer_id.in_(customer_ids),
SalesOrder.order_status != "canceled",
)
.group_by(SalesOrder.customer_id)
).all()
amount_map = {row.customer_id: float(row.total_amount) for row in amount_rows}
# 批量查每个客户最近一笔订单(窗口函数,替代逐个 N+1
from sqlalchemy import over
latest_order_sq = (
select(
SalesOrder.customer_id,
SalesOrder.id.label("order_id"),
SalesOrder.created_at,
over(func.row_number(), partition_by=SalesOrder.customer_id,
order_by=(SalesOrder.created_at.desc(), SalesOrder.id.desc())).label("rn"),
)
.where(
SalesOrder.deleted == 0,
SalesOrder.customer_id.in_(customer_ids),
SalesOrder.order_status != "canceled",
)
.subquery()
)
latest_rows = session.execute(
select(latest_order_sq).where(latest_order_sq.c.rn == 1)
).all()
latest_order_map = {row.customer_id: row.order_id for row in latest_rows}
# 组装结果,用已查到的 order_id 批量加载 SalesOrder 对象
candidate_order_ids = [oid for oid in latest_order_map.values() if oid is not None]
if candidate_order_ids:
order_objs = session.execute(
select(SalesOrder).where(SalesOrder.id.in_(candidate_order_ids))
).scalars().all()
order_obj_map = {o.id: o for o in order_objs}
else:
order_obj_map = {}
results: list[tuple[Customer, SalesOrder | None, float]] = []
for customer in customers:
total_amount_stmt = (
select(func.coalesce(func.sum(SalesOrder.contract_amount), 0))
.where(
SalesOrder.deleted == 0,
SalesOrder.customer_id == customer.id,
SalesOrder.order_status != "canceled",
)
)
total_amount = float(session.execute(total_amount_stmt).scalar() or 0)
latest_order_stmt = (
select(SalesOrder)
.where(
SalesOrder.deleted == 0,
SalesOrder.customer_id == customer.id,
SalesOrder.order_status != "canceled",
)
.order_by(SalesOrder.created_at.desc(), SalesOrder.id.desc())
)
latest_order = session.execute(latest_order_stmt).scalars().first()
total_amount = amount_map.get(customer.id, 0.0)
latest_order_id = latest_order_map.get(customer.id)
latest_order = order_obj_map.get(latest_order_id) if latest_order_id else None
if latest_order is None or (latest_order.created_at and latest_order.created_at <= inactive_before):
results.append((customer, latest_order, total_amount))
return results

View File

@ -55,16 +55,14 @@ class ReminderService:
if cached is not None:
return cached
try:
reminders = self.repository.list_reminders(session, filters)
reminders, total = self.repository.list_reminders(session, filters)
page_no = filters.get("page_no", 1)
page_size = filters.get("page_size", 20)
start = max(page_no - 1, 0) * page_size
page_list = reminders[start : start + page_size]
result = {
"total": len(reminders),
"total": total,
"page_no": page_no,
"page_size": page_size,
"list": [self._map_reminder(item) for item in page_list],
"list": [self._map_reminder(item) for item in reminders],
}
cache_set(cache_key, result, ttl=30)
return result